Skip to content

Module plasmid_assessor.Assessment

View Source
import re

import matplotlib.pyplot as plt

import Bio

import Bio.Restriction

from Bio.Seq import Seq

from Bio.SeqFeature import SeqFeature, FeatureLocation

import dnacauldron as dc

try:

    from dna_features_viewer import BiopythonTranslator

except ImportError:

    class AssessmentTranslator:

        """Please install dna_features_viewer to use this class."""

        def __init__(self):

            raise Exception("Please install dna_features_viewer to use this class.")

else:

    class AssessmentTranslator(BiopythonTranslator):

        """Custom translator for highlighting key features."""

        def compute_feature_color(self, feature):

            assessment_ref = "plasmid_assessment"

            if assessment_ref in feature.qualifiers:

                if feature.qualifiers[assessment_ref] == "enzyme":

                    return "red"

                elif feature.qualifiers[assessment_ref] == "excised":

                    return "yellow"

                elif feature.qualifiers[assessment_ref] == "backbone":

                    return "tab:cyan"

                else:

                    return "tab:blue"  # default dna_features_viewer color

            else:

                return "tab:blue"

class Assessment:

    """The plasmid assessment class.

    **Parameters**

    **record**

    > A Biopython `SeqRecord`.

    **enzyme**

    > A restriction enzyme (`str`). A Biopython `RestrictionType` will be looked

    up using the string.

    """

    UNKNOWN_IDS = [

        "None",

        "",

        "<unknown id>",

        ".",

        "EXPORTED",

        "<unknown name>",

        "Exported",

    ]

    def __init__(self, record, enzyme):

        self.record = record

        self.enzyme = Bio.Restriction.__dict__[enzyme]

        self.enzyme_name = str(self.enzyme)

        self.results = {}

    def assess_plasmid(self, other_enzymes=None):

        """Evaluate plasmid for Golden Gate.

        **Parameters**

        **other_enzymes**

        > List of enzymes used in higher level assemblies (`list`).

        """

        if other_enzymes:

            self.other_enzymes = ", ".join([str(enz) for enz in other_enzymes])

        self.add_name()

        self.check_circularity()

        self.get_number_of_sites()

        self.evaluate_orientation()

        self.digest_plasmid()

        self.count_other_sites(other_enzymes)

        self.check_enzyme_site_locations()

        self.sum_results()

        self.plot_plasmid()

    def add_name(self):

        """Set a name for the assessment."""

        # To display on the report:

        if str(self.record.id).strip() in self.UNKNOWN_IDS:

            self.name = "Unnamed plasmid"

        else:

            if len(self.record.id) > 16:  # Genbank limit, also for width in report

                self.name = self.record.id[:16] + "..."

            else:

                self.name = self.record.id

    def check_circularity(self):

        if "topology" not in self.record.annotations:

            self.results["is_circular"] = False

        elif self.record.annotations["topology"] == "circular":

            self.results["is_circular"] = True

        else:

            self.results["is_circular"] = False

    def get_number_of_sites(self):

        if "is_circular" in self.results:

            is_linear = not self.results["is_circular"]

        else:

            is_linear = False

        restriction_batch = Bio.Restriction.RestrictionBatch([self.enzyme])

        analysis = Bio.Restriction.Analysis(

            restriction_batch, sequence=self.record.seq, linear=is_linear

        )

        self.analysis_results = analysis.full(linear=is_linear)

        self.results["number_of_sites"] = len(self.analysis_results[self.enzyme])

        # Add as features for plot in report:

        for enzyme, sites in self.analysis_results.items():

            for site in sites:

                self.record.features.append(

                    SeqFeature(

                        FeatureLocation(site, site + 1),

                        id=str(enzyme),

                        type="misc_feature",

                        qualifiers={

                            "label": str(enzyme),

                            "plasmid_assessment": "enzyme",

                        },

                    )

                )

    def evaluate_orientation(self):

        self.results["is_site_orientation_correct"] = False  # default

        # Forward strand:

        self.iter_forward = [

            match.end() for match in re.finditer(self.enzyme.site, str(self.record.seq))

        ]

        if sum(1 for _ in self.iter_forward) == 1:

            self.forward_enzyme = self.iter_forward[0]

            # rev_complement_site = str(self.record.seq.reverse_complement())

            rev_complement_site = str(Seq(self.enzyme.site).reverse_complement())

            self.iter_reverse = [

                m.start()

                for m in re.finditer(rev_complement_site, str(self.record.seq))

            ]

            if sum(1 for _ in self.iter_reverse) == 1:  # 1 site in both strands:

                self.results["is_site_orientation_correct"] = True

                self.reverse_enzyme = self.iter_reverse[0]

        if self.results["is_site_orientation_correct"]:

            if self.reverse_enzyme < self.forward_enzyme:

                self.record.features.append(

                    SeqFeature(

                        FeatureLocation(

                            self.reverse_enzyme - 1, self.forward_enzyme + 1

                        ),

                        id=str(self.enzyme),

                        type="misc_feature",

                        qualifiers={

                            "label": "Excised",

                            "plasmid_assessment": "excised",

                        },

                    )

                )

            else:  # put annotation together from two pieces:

                self.record.features.append(

                    SeqFeature(

                        FeatureLocation(0, self.forward_enzyme + 1),

                        id=str(self.enzyme),

                        type="misc_feature",

                        qualifiers={

                            "label": "Excised",

                            "plasmid_assessment": "excised",

                        },

                    )

                )

                self.record.features.append(

                    SeqFeature(

                        FeatureLocation(self.reverse_enzyme - 1, len(self.record)),

                        id=str(self.enzyme),

                        type="misc_feature",

                        qualifiers={

                            "label": "Excised",

                            "plasmid_assessment": "excised",

                        },

                    )

                )

    def digest_plasmid(self):

        # Obtain fragments and get the backbone's overhangs.

        # This method has two assumptions:

        # - the sequence has two, correctly oriented enzyme sites.

        # - the sequence is circular.

        # Therefore there will be exactly two fragments, with one containing both sites.

        self.results["digest"] = {}

        if not self.results["is_circular"]:

            return

        if not self.results["is_site_orientation_correct"]:

            return

        record_fragments = dc.StickyEndFragment.list_from_record_digestion(

            record=self.record, enzyme=self.enzyme, linear=False

        )

        if self.enzyme.site in record_fragments[0].to_standard_string():

            backbone_index = 1  # there are only two fragments

            excise_index = 0

        else:

            backbone_index = 0

            excise_index = 1  # reversed

        self.results["digest"]["backbone_seq"] = record_fragments[backbone_index]

        self.results["digest"]["excised_seq"] = record_fragments[excise_index]

        self.results["digest"]["first_overhang"] = str(

            record_fragments[excise_index].seq.left_end

        )

        self.results["digest"]["last_overhang"] = str(

            record_fragments[excise_index].seq.right_end

        )

    def count_other_sites(self, other_enzymes):

        self.results["other_sites"] = {}

        self.results["other_sites"]["has_any_other_sites"] = False

        if other_enzymes is None:

            return

        bio_enzymes = [Bio.Restriction.__dict__[enzyme] for enzyme in other_enzymes]

        restriction_batch = Bio.Restriction.RestrictionBatch(bio_enzymes)

        # Work with the assumption that the sequence is circular:

        analysis = Bio.Restriction.Analysis(

            restriction_batch, sequence=self.record.seq, linear=False

        )

        self.results["other_sites"]["enzyme"] = analysis.full(linear=False)

        for enzyme, matches in self.results["other_sites"]["enzyme"].items():

            if len(matches) != 0:

                self.results["other_sites"]["has_any_other_sites"] = True

                # Also add as features for plot in report:

                for site in matches:

                    self.record.features.append(

                        SeqFeature(

                            FeatureLocation(site, site + 1),

                            id=str(enzyme),

                            type="misc_feature",

                            qualifiers={

                                "label": str(enzyme),

                                "plasmid_assessment": "enzyme",

                            },

                        )

                    )

    def check_enzyme_site_locations(self):

        """Flag enzyme sites that are within the retained backbone."""

        try:

            self.results["other_sites"]["has_any_other_sites"]

            self.results["is_site_orientation_correct"]

        except KeyError:

            print("Run assessment methods first!")

        else:

            self.sites_outside_excised_region = {}

            if (

                self.results["other_sites"]["has_any_other_sites"]

                and self.results["is_site_orientation_correct"]

            ):

                # if there are no other sites, no need to run:

                if self.reverse_enzyme < self.forward_enzyme:

                    # orientation = reverse -> forward

                    for enzyme, sites in self.results["other_sites"]["enzyme"].items():

                        problem_sites = []

                        for site in sites:

                            if self.reverse_enzyme < site < self.forward_enzyme:

                                pass

                            else:

                                problem_sites += [str(site)]

                        if problem_sites != []:

                            self.sites_outside_excised_region[

                                str(enzyme)

                            ] = problem_sites

                    txt = ""  # for the pdf report

                    for (

                        enzyme,

                        problem_sites,

                    ) in self.sites_outside_excised_region.items():

                        txt += enzyme + ": " + " ".join(problem_sites) + ";"

                    self.sites_outside_excised_region_txt = txt

                else:

                    # orientation = forward -> reverse

                    for enzyme, sites in self.results["other_sites"]["enzyme"].items():

                        problem_sites = []

                        for site in sites:

                            if self.forward_enzyme < site < self.reverse_enzyme:

                                # in this case the site is within the retained backbone

                                problem_sites += [str(site)]

                        if problem_sites != []:

                            self.sites_outside_excised_region[

                                str(enzyme)

                            ] = problem_sites

                    txt = ""  # for the pdf report

                    for (

                        enzyme,

                        problem_sites,

                    ) in self.sites_outside_excised_region.items():

                        txt += enzyme + ": " + " ".join(problem_sites) + ";"

                    self.sites_outside_excised_region_txt = txt

            else:  # no other sites or orientation not correct

                self.sites_outside_excised_region_txt = ""

    def sum_results(self):

        self.results["pass"] = True

        if self.results["is_circular"] is False:

            self.results["pass"] = False

            return

        if self.results["is_site_orientation_correct"] is False:

            # implicitly checks number of sites too

            self.results["pass"] = False

            return

        if self.sites_outside_excised_region_txt:

            self.results["pass"] = False

            return

        # if self.results["other_sites"]["has_any_other_sites"]:

        #     self.results["pass"] = False

        #     return

    def plot_plasmid(self):

        """Plot an outline of the plasmid."""

        fig, ax = plt.subplots(figsize=(7, 4))

        graphic_record = AssessmentTranslator().translate_record(self.record)

        graphic_record.plot(ax=ax, with_ruler=False, strand_in_label_threshold=2)

        self.fig = fig

Classes

Assessment

class Assessment(
    record,
    enzyme
)

The plasmid assessment class.

Parameters

record

A Biopython SeqRecord.

enzyme

A restriction enzyme (str). A Biopython RestrictionType will be looked up using the string.

View Source
class Assessment:

    """The plasmid assessment class.

    **Parameters**

    **record**

    > A Biopython `SeqRecord`.

    **enzyme**

    > A restriction enzyme (`str`). A Biopython `RestrictionType` will be looked

    up using the string.

    """

    UNKNOWN_IDS = [

        "None",

        "",

        "<unknown id>",

        ".",

        "EXPORTED",

        "<unknown name>",

        "Exported",

    ]

    def __init__(self, record, enzyme):

        self.record = record

        self.enzyme = Bio.Restriction.__dict__[enzyme]

        self.enzyme_name = str(self.enzyme)

        self.results = {}

    def assess_plasmid(self, other_enzymes=None):

        """Evaluate plasmid for Golden Gate.

        **Parameters**

        **other_enzymes**

        > List of enzymes used in higher level assemblies (`list`).

        """

        if other_enzymes:

            self.other_enzymes = ", ".join([str(enz) for enz in other_enzymes])

        self.add_name()

        self.check_circularity()

        self.get_number_of_sites()

        self.evaluate_orientation()

        self.digest_plasmid()

        self.count_other_sites(other_enzymes)

        self.check_enzyme_site_locations()

        self.sum_results()

        self.plot_plasmid()

    def add_name(self):

        """Set a name for the assessment."""

        # To display on the report:

        if str(self.record.id).strip() in self.UNKNOWN_IDS:

            self.name = "Unnamed plasmid"

        else:

            if len(self.record.id) > 16:  # Genbank limit, also for width in report

                self.name = self.record.id[:16] + "..."

            else:

                self.name = self.record.id

    def check_circularity(self):

        if "topology" not in self.record.annotations:

            self.results["is_circular"] = False

        elif self.record.annotations["topology"] == "circular":

            self.results["is_circular"] = True

        else:

            self.results["is_circular"] = False

    def get_number_of_sites(self):

        if "is_circular" in self.results:

            is_linear = not self.results["is_circular"]

        else:

            is_linear = False

        restriction_batch = Bio.Restriction.RestrictionBatch([self.enzyme])

        analysis = Bio.Restriction.Analysis(

            restriction_batch, sequence=self.record.seq, linear=is_linear

        )

        self.analysis_results = analysis.full(linear=is_linear)

        self.results["number_of_sites"] = len(self.analysis_results[self.enzyme])

        # Add as features for plot in report:

        for enzyme, sites in self.analysis_results.items():

            for site in sites:

                self.record.features.append(

                    SeqFeature(

                        FeatureLocation(site, site + 1),

                        id=str(enzyme),

                        type="misc_feature",

                        qualifiers={

                            "label": str(enzyme),

                            "plasmid_assessment": "enzyme",

                        },

                    )

                )

    def evaluate_orientation(self):

        self.results["is_site_orientation_correct"] = False  # default

        # Forward strand:

        self.iter_forward = [

            match.end() for match in re.finditer(self.enzyme.site, str(self.record.seq))

        ]

        if sum(1 for _ in self.iter_forward) == 1:

            self.forward_enzyme = self.iter_forward[0]

            # rev_complement_site = str(self.record.seq.reverse_complement())

            rev_complement_site = str(Seq(self.enzyme.site).reverse_complement())

            self.iter_reverse = [

                m.start()

                for m in re.finditer(rev_complement_site, str(self.record.seq))

            ]

            if sum(1 for _ in self.iter_reverse) == 1:  # 1 site in both strands:

                self.results["is_site_orientation_correct"] = True

                self.reverse_enzyme = self.iter_reverse[0]

        if self.results["is_site_orientation_correct"]:

            if self.reverse_enzyme < self.forward_enzyme:

                self.record.features.append(

                    SeqFeature(

                        FeatureLocation(

                            self.reverse_enzyme - 1, self.forward_enzyme + 1

                        ),

                        id=str(self.enzyme),

                        type="misc_feature",

                        qualifiers={

                            "label": "Excised",

                            "plasmid_assessment": "excised",

                        },

                    )

                )

            else:  # put annotation together from two pieces:

                self.record.features.append(

                    SeqFeature(

                        FeatureLocation(0, self.forward_enzyme + 1),

                        id=str(self.enzyme),

                        type="misc_feature",

                        qualifiers={

                            "label": "Excised",

                            "plasmid_assessment": "excised",

                        },

                    )

                )

                self.record.features.append(

                    SeqFeature(

                        FeatureLocation(self.reverse_enzyme - 1, len(self.record)),

                        id=str(self.enzyme),

                        type="misc_feature",

                        qualifiers={

                            "label": "Excised",

                            "plasmid_assessment": "excised",

                        },

                    )

                )

    def digest_plasmid(self):

        # Obtain fragments and get the backbone's overhangs.

        # This method has two assumptions:

        # - the sequence has two, correctly oriented enzyme sites.

        # - the sequence is circular.

        # Therefore there will be exactly two fragments, with one containing both sites.

        self.results["digest"] = {}

        if not self.results["is_circular"]:

            return

        if not self.results["is_site_orientation_correct"]:

            return

        record_fragments = dc.StickyEndFragment.list_from_record_digestion(

            record=self.record, enzyme=self.enzyme, linear=False

        )

        if self.enzyme.site in record_fragments[0].to_standard_string():

            backbone_index = 1  # there are only two fragments

            excise_index = 0

        else:

            backbone_index = 0

            excise_index = 1  # reversed

        self.results["digest"]["backbone_seq"] = record_fragments[backbone_index]

        self.results["digest"]["excised_seq"] = record_fragments[excise_index]

        self.results["digest"]["first_overhang"] = str(

            record_fragments[excise_index].seq.left_end

        )

        self.results["digest"]["last_overhang"] = str(

            record_fragments[excise_index].seq.right_end

        )

    def count_other_sites(self, other_enzymes):

        self.results["other_sites"] = {}

        self.results["other_sites"]["has_any_other_sites"] = False

        if other_enzymes is None:

            return

        bio_enzymes = [Bio.Restriction.__dict__[enzyme] for enzyme in other_enzymes]

        restriction_batch = Bio.Restriction.RestrictionBatch(bio_enzymes)

        # Work with the assumption that the sequence is circular:

        analysis = Bio.Restriction.Analysis(

            restriction_batch, sequence=self.record.seq, linear=False

        )

        self.results["other_sites"]["enzyme"] = analysis.full(linear=False)

        for enzyme, matches in self.results["other_sites"]["enzyme"].items():

            if len(matches) != 0:

                self.results["other_sites"]["has_any_other_sites"] = True

                # Also add as features for plot in report:

                for site in matches:

                    self.record.features.append(

                        SeqFeature(

                            FeatureLocation(site, site + 1),

                            id=str(enzyme),

                            type="misc_feature",

                            qualifiers={

                                "label": str(enzyme),

                                "plasmid_assessment": "enzyme",

                            },

                        )

                    )

    def check_enzyme_site_locations(self):

        """Flag enzyme sites that are within the retained backbone."""

        try:

            self.results["other_sites"]["has_any_other_sites"]

            self.results["is_site_orientation_correct"]

        except KeyError:

            print("Run assessment methods first!")

        else:

            self.sites_outside_excised_region = {}

            if (

                self.results["other_sites"]["has_any_other_sites"]

                and self.results["is_site_orientation_correct"]

            ):

                # if there are no other sites, no need to run:

                if self.reverse_enzyme < self.forward_enzyme:

                    # orientation = reverse -> forward

                    for enzyme, sites in self.results["other_sites"]["enzyme"].items():

                        problem_sites = []

                        for site in sites:

                            if self.reverse_enzyme < site < self.forward_enzyme:

                                pass

                            else:

                                problem_sites += [str(site)]

                        if problem_sites != []:

                            self.sites_outside_excised_region[

                                str(enzyme)

                            ] = problem_sites

                    txt = ""  # for the pdf report

                    for (

                        enzyme,

                        problem_sites,

                    ) in self.sites_outside_excised_region.items():

                        txt += enzyme + ": " + " ".join(problem_sites) + ";"

                    self.sites_outside_excised_region_txt = txt

                else:

                    # orientation = forward -> reverse

                    for enzyme, sites in self.results["other_sites"]["enzyme"].items():

                        problem_sites = []

                        for site in sites:

                            if self.forward_enzyme < site < self.reverse_enzyme:

                                # in this case the site is within the retained backbone

                                problem_sites += [str(site)]

                        if problem_sites != []:

                            self.sites_outside_excised_region[

                                str(enzyme)

                            ] = problem_sites

                    txt = ""  # for the pdf report

                    for (

                        enzyme,

                        problem_sites,

                    ) in self.sites_outside_excised_region.items():

                        txt += enzyme + ": " + " ".join(problem_sites) + ";"

                    self.sites_outside_excised_region_txt = txt

            else:  # no other sites or orientation not correct

                self.sites_outside_excised_region_txt = ""

    def sum_results(self):

        self.results["pass"] = True

        if self.results["is_circular"] is False:

            self.results["pass"] = False

            return

        if self.results["is_site_orientation_correct"] is False:

            # implicitly checks number of sites too

            self.results["pass"] = False

            return

        if self.sites_outside_excised_region_txt:

            self.results["pass"] = False

            return

        # if self.results["other_sites"]["has_any_other_sites"]:

        #     self.results["pass"] = False

        #     return

    def plot_plasmid(self):

        """Plot an outline of the plasmid."""

        fig, ax = plt.subplots(figsize=(7, 4))

        graphic_record = AssessmentTranslator().translate_record(self.record)

        graphic_record.plot(ax=ax, with_ruler=False, strand_in_label_threshold=2)

        self.fig = fig

Class variables

UNKNOWN_IDS

Methods

add_name
def add_name(
    self
)

Set a name for the assessment.

View Source
    def add_name(self):

        """Set a name for the assessment."""

        # To display on the report:

        if str(self.record.id).strip() in self.UNKNOWN_IDS:

            self.name = "Unnamed plasmid"

        else:

            if len(self.record.id) > 16:  # Genbank limit, also for width in report

                self.name = self.record.id[:16] + "..."

            else:

                self.name = self.record.id
assess_plasmid
def assess_plasmid(
    self,
    other_enzymes=None
)

Evaluate plasmid for Golden Gate.

Parameters

other_enzymes

List of enzymes used in higher level assemblies (list).

View Source
    def assess_plasmid(self, other_enzymes=None):

        """Evaluate plasmid for Golden Gate.

        **Parameters**

        **other_enzymes**

        > List of enzymes used in higher level assemblies (`list`).

        """

        if other_enzymes:

            self.other_enzymes = ", ".join([str(enz) for enz in other_enzymes])

        self.add_name()

        self.check_circularity()

        self.get_number_of_sites()

        self.evaluate_orientation()

        self.digest_plasmid()

        self.count_other_sites(other_enzymes)

        self.check_enzyme_site_locations()

        self.sum_results()

        self.plot_plasmid()
check_circularity
def check_circularity(
    self
)
View Source
    def check_circularity(self):

        if "topology" not in self.record.annotations:

            self.results["is_circular"] = False

        elif self.record.annotations["topology"] == "circular":

            self.results["is_circular"] = True

        else:

            self.results["is_circular"] = False
check_enzyme_site_locations
def check_enzyme_site_locations(
    self
)

Flag enzyme sites that are within the retained backbone.

View Source
    def check_enzyme_site_locations(self):

        """Flag enzyme sites that are within the retained backbone."""

        try:

            self.results["other_sites"]["has_any_other_sites"]

            self.results["is_site_orientation_correct"]

        except KeyError:

            print("Run assessment methods first!")

        else:

            self.sites_outside_excised_region = {}

            if (

                self.results["other_sites"]["has_any_other_sites"]

                and self.results["is_site_orientation_correct"]

            ):

                # if there are no other sites, no need to run:

                if self.reverse_enzyme < self.forward_enzyme:

                    # orientation = reverse -> forward

                    for enzyme, sites in self.results["other_sites"]["enzyme"].items():

                        problem_sites = []

                        for site in sites:

                            if self.reverse_enzyme < site < self.forward_enzyme:

                                pass

                            else:

                                problem_sites += [str(site)]

                        if problem_sites != []:

                            self.sites_outside_excised_region[

                                str(enzyme)

                            ] = problem_sites

                    txt = ""  # for the pdf report

                    for (

                        enzyme,

                        problem_sites,

                    ) in self.sites_outside_excised_region.items():

                        txt += enzyme + ": " + " ".join(problem_sites) + ";"

                    self.sites_outside_excised_region_txt = txt

                else:

                    # orientation = forward -> reverse

                    for enzyme, sites in self.results["other_sites"]["enzyme"].items():

                        problem_sites = []

                        for site in sites:

                            if self.forward_enzyme < site < self.reverse_enzyme:

                                # in this case the site is within the retained backbone

                                problem_sites += [str(site)]

                        if problem_sites != []:

                            self.sites_outside_excised_region[

                                str(enzyme)

                            ] = problem_sites

                    txt = ""  # for the pdf report

                    for (

                        enzyme,

                        problem_sites,

                    ) in self.sites_outside_excised_region.items():

                        txt += enzyme + ": " + " ".join(problem_sites) + ";"

                    self.sites_outside_excised_region_txt = txt

            else:  # no other sites or orientation not correct

                self.sites_outside_excised_region_txt = ""
count_other_sites
def count_other_sites(
    self,
    other_enzymes
)
View Source
    def count_other_sites(self, other_enzymes):

        self.results["other_sites"] = {}

        self.results["other_sites"]["has_any_other_sites"] = False

        if other_enzymes is None:

            return

        bio_enzymes = [Bio.Restriction.__dict__[enzyme] for enzyme in other_enzymes]

        restriction_batch = Bio.Restriction.RestrictionBatch(bio_enzymes)

        # Work with the assumption that the sequence is circular:

        analysis = Bio.Restriction.Analysis(

            restriction_batch, sequence=self.record.seq, linear=False

        )

        self.results["other_sites"]["enzyme"] = analysis.full(linear=False)

        for enzyme, matches in self.results["other_sites"]["enzyme"].items():

            if len(matches) != 0:

                self.results["other_sites"]["has_any_other_sites"] = True

                # Also add as features for plot in report:

                for site in matches:

                    self.record.features.append(

                        SeqFeature(

                            FeatureLocation(site, site + 1),

                            id=str(enzyme),

                            type="misc_feature",

                            qualifiers={

                                "label": str(enzyme),

                                "plasmid_assessment": "enzyme",

                            },

                        )

                    )
digest_plasmid
def digest_plasmid(
    self
)
View Source
    def digest_plasmid(self):

        # Obtain fragments and get the backbone's overhangs.

        # This method has two assumptions:

        # - the sequence has two, correctly oriented enzyme sites.

        # - the sequence is circular.

        # Therefore there will be exactly two fragments, with one containing both sites.

        self.results["digest"] = {}

        if not self.results["is_circular"]:

            return

        if not self.results["is_site_orientation_correct"]:

            return

        record_fragments = dc.StickyEndFragment.list_from_record_digestion(

            record=self.record, enzyme=self.enzyme, linear=False

        )

        if self.enzyme.site in record_fragments[0].to_standard_string():

            backbone_index = 1  # there are only two fragments

            excise_index = 0

        else:

            backbone_index = 0

            excise_index = 1  # reversed

        self.results["digest"]["backbone_seq"] = record_fragments[backbone_index]

        self.results["digest"]["excised_seq"] = record_fragments[excise_index]

        self.results["digest"]["first_overhang"] = str(

            record_fragments[excise_index].seq.left_end

        )

        self.results["digest"]["last_overhang"] = str(

            record_fragments[excise_index].seq.right_end

        )
evaluate_orientation
def evaluate_orientation(
    self
)
View Source
    def evaluate_orientation(self):

        self.results["is_site_orientation_correct"] = False  # default

        # Forward strand:

        self.iter_forward = [

            match.end() for match in re.finditer(self.enzyme.site, str(self.record.seq))

        ]

        if sum(1 for _ in self.iter_forward) == 1:

            self.forward_enzyme = self.iter_forward[0]

            # rev_complement_site = str(self.record.seq.reverse_complement())

            rev_complement_site = str(Seq(self.enzyme.site).reverse_complement())

            self.iter_reverse = [

                m.start()

                for m in re.finditer(rev_complement_site, str(self.record.seq))

            ]

            if sum(1 for _ in self.iter_reverse) == 1:  # 1 site in both strands:

                self.results["is_site_orientation_correct"] = True

                self.reverse_enzyme = self.iter_reverse[0]

        if self.results["is_site_orientation_correct"]:

            if self.reverse_enzyme < self.forward_enzyme:

                self.record.features.append(

                    SeqFeature(

                        FeatureLocation(

                            self.reverse_enzyme - 1, self.forward_enzyme + 1

                        ),

                        id=str(self.enzyme),

                        type="misc_feature",

                        qualifiers={

                            "label": "Excised",

                            "plasmid_assessment": "excised",

                        },

                    )

                )

            else:  # put annotation together from two pieces:

                self.record.features.append(

                    SeqFeature(

                        FeatureLocation(0, self.forward_enzyme + 1),

                        id=str(self.enzyme),

                        type="misc_feature",

                        qualifiers={

                            "label": "Excised",

                            "plasmid_assessment": "excised",

                        },

                    )

                )

                self.record.features.append(

                    SeqFeature(

                        FeatureLocation(self.reverse_enzyme - 1, len(self.record)),

                        id=str(self.enzyme),

                        type="misc_feature",

                        qualifiers={

                            "label": "Excised",

                            "plasmid_assessment": "excised",

                        },

                    )

                )
get_number_of_sites
def get_number_of_sites(
    self
)
View Source
    def get_number_of_sites(self):

        if "is_circular" in self.results:

            is_linear = not self.results["is_circular"]

        else:

            is_linear = False

        restriction_batch = Bio.Restriction.RestrictionBatch([self.enzyme])

        analysis = Bio.Restriction.Analysis(

            restriction_batch, sequence=self.record.seq, linear=is_linear

        )

        self.analysis_results = analysis.full(linear=is_linear)

        self.results["number_of_sites"] = len(self.analysis_results[self.enzyme])

        # Add as features for plot in report:

        for enzyme, sites in self.analysis_results.items():

            for site in sites:

                self.record.features.append(

                    SeqFeature(

                        FeatureLocation(site, site + 1),

                        id=str(enzyme),

                        type="misc_feature",

                        qualifiers={

                            "label": str(enzyme),

                            "plasmid_assessment": "enzyme",

                        },

                    )

                )
plot_plasmid
def plot_plasmid(
    self
)

Plot an outline of the plasmid.

View Source
    def plot_plasmid(self):

        """Plot an outline of the plasmid."""

        fig, ax = plt.subplots(figsize=(7, 4))

        graphic_record = AssessmentTranslator().translate_record(self.record)

        graphic_record.plot(ax=ax, with_ruler=False, strand_in_label_threshold=2)

        self.fig = fig
sum_results
def sum_results(
    self
)
View Source
    def sum_results(self):

        self.results["pass"] = True

        if self.results["is_circular"] is False:

            self.results["pass"] = False

            return

        if self.results["is_site_orientation_correct"] is False:

            # implicitly checks number of sites too

            self.results["pass"] = False

            return

        if self.sites_outside_excised_region_txt:

            self.results["pass"] = False

            return

AssessmentTranslator

class AssessmentTranslator(
    features_filters=(),
    features_properties=None
)

Custom translator for highlighting key features.

View Source
    class AssessmentTranslator:

        """Please install dna_features_viewer to use this class."""

        def __init__(self):

            raise Exception("Please install dna_features_viewer to use this class.")

Ancestors (in MRO)

  • dna_features_viewer.BiopythonTranslator.BiopythonTranslator.BiopythonTranslator
  • dna_features_viewer.BiopythonTranslator.BiopythonTranslatorBase.BiopythonTranslatorBase

Class variables

default_feature_color
graphic_record_parameters
ignored_features_types
label_fields

Static methods

quick_class_plot
def quick_class_plot(
    record,
    figure_width=12,
    **kwargs
)

Allows super quick and dirty plotting of Biopython records.

This is really meant for use in a Jupyter/Ipython notebook with the "%matplotlib inline" setting.

from dna_features_viewer import BiopythonTranslator BiopythonTranslator.quick_plot(my_record)

View Source
    @classmethod

    def quick_class_plot(cls, record, figure_width=12, **kwargs):

        """Allows super quick and dirty plotting of Biopython records.

        This is really meant for use in a Jupyter/Ipython notebook with

        the "%matplotlib inline" setting.

        >>> from dna_features_viewer import BiopythonTranslator

        >>> BiopythonTranslator.quick_plot(my_record)

        """

        graphic_record = cls().translate_record(record)

        ax, _ = graphic_record.plot(figure_width=figure_width, **kwargs)

        return ax

Methods

compute_feature_box_color
def compute_feature_box_color(
    self,
    feature
)

Compute a box_color for this feature.

View Source
    def compute_feature_box_color(self, feature):

        """Compute a box_color for this feature."""

        return "auto"
compute_feature_box_linewidth
def compute_feature_box_linewidth(
    self,
    feature
)

Compute a box_linewidth for this feature.

View Source
    def compute_feature_box_linewidth(self, feature):

        """Compute a box_linewidth for this feature."""

        return 0.3
compute_feature_color
def compute_feature_color(
    self,
    feature
)

Compute a color for this feature.

If the feature has a color qualifier it will be used. Otherwise, the classe's default_feature_color is used.

To change the behaviour, create a subclass of BiopythonTranslator and overwrite this method.

View Source
        def compute_feature_color(self, feature):

            assessment_ref = "plasmid_assessment"

            if assessment_ref in feature.qualifiers:

                if feature.qualifiers[assessment_ref] == "enzyme":

                    return "red"

                elif feature.qualifiers[assessment_ref] == "excised":

                    return "yellow"

                elif feature.qualifiers[assessment_ref] == "backbone":

                    return "tab:cyan"

                else:

                    return "tab:blue"  # default dna_features_viewer color

            else:

                return "tab:blue"
compute_feature_fontdict
def compute_feature_fontdict(
    self,
    feature
)

Compute a font dict for this feature.

View Source
    def compute_feature_fontdict(self, feature):

        """Compute a font dict for this feature."""

        return None
compute_feature_html
def compute_feature_html(
    self,
    feature
)

Gets the 'label' of the feature.

View Source
    def compute_feature_html(self, feature):

        """Gets the 'label' of the feature."""

        return self.compute_feature_label(feature)
compute_feature_label
def compute_feature_label(
    self,
    feature
)

Compute the label of the feature.

View Source
    def compute_feature_label(self, feature):

        """Compute the label of the feature."""

        label = feature.type

        for key in self.label_fields:

            if key in feature.qualifiers and len(feature.qualifiers[key]):

                label = feature.qualifiers[key]

                break

        if isinstance(label, list):

            label = "|".join(label)

        return label
def compute_feature_label_link_color(
    self,
    feature
)

Compute the color of the line linking the label to its feature.

View Source
    def compute_feature_label_link_color(self, feature):

        """Compute the color of the line linking the label to its feature."""

        return "black"
compute_feature_legend_text
def compute_feature_legend_text(
    self,
    feature
)
View Source
    def compute_feature_legend_text(self, feature):

        return None
compute_feature_linewidth
def compute_feature_linewidth(
    self,
    feature
)

Compute the edge width of the feature's arrow/rectangle.

View Source
    def compute_feature_linewidth(self, feature):

        """Compute the edge width of the feature's arrow/rectangle."""

        return 1.0
compute_filtered_features
def compute_filtered_features(
    self,
    features
)

Return the list of features minus the ignored ones.

By the method keeps any feature whose type is not in ignored_features_types and for which all filter(f) pass.

View Source
    def compute_filtered_features(self, features):

        """Return the list of features minus the ignored ones.

        By the method keeps any feature whose type is not in

        ignored_features_types and for which all filter(f) pass.

        """

        return [

            f

            for f in features

            if all([fl(f) for fl in self.features_filters])

            and f.type not in self.ignored_features_types

        ]
quick_plot
def quick_plot(
    self,
    record,
    figure_width=12,
    **kwargs
)

Allows super quick and dirty plotting of Biopython records.

This is really meant for use in a Jupyter/Ipython notebook with the "%matplotlib inline" setting.

from dna_features_viewer import BiopythonTranslator BiopythonTranslator.quick_plot(my_record)

View Source
    def quick_plot(self, record, figure_width=12, **kwargs):

        """Allows super quick and dirty plotting of Biopython records.

        This is really meant for use in a Jupyter/Ipython notebook with

        the "%matplotlib inline" setting.

        >>> from dna_features_viewer import BiopythonTranslator

        >>> BiopythonTranslator.quick_plot(my_record)

        """

        graphic_record = self.translate_record(record)

        ax, _ = graphic_record.plot(figure_width=figure_width, **kwargs)

        return ax
translate_feature
def translate_feature(
    self,
    feature
)

Translate a Biopython feature into a Dna Features Viewer feature.

View Source
    def translate_feature(self, feature):

        """Translate a Biopython feature into a Dna Features Viewer feature."""

        properties = dict(

            label=self.compute_feature_label(feature),

            color=self.compute_feature_color(feature),

            html=self.compute_feature_html(feature),

            fontdict=self.compute_feature_fontdict(feature),

            box_linewidth=self.compute_feature_box_linewidth(feature),

            box_color=self.compute_feature_box_color(feature),

            linewidth=self.compute_feature_linewidth(feature),

            label_link_color=self.compute_feature_label_link_color(feature),

            legend_text=self.compute_feature_legend_text(feature),

        )

        if self.features_properties is not None:

            other_properties = self.features_properties

            if hasattr(other_properties, "__call__"):

                other_properties = other_properties(feature)

            properties.update(other_properties)

        return GraphicFeature(

            start=feature.location.start,

            end=feature.location.end,

            strand=feature.location.strand,

            **properties

        )
translate_record
def translate_record(
    self,
    record,
    record_class=None,
    filetype=None
)

Create a new GraphicRecord from a BioPython Record object.

Parameters

record A BioPython Record object or the path to a Genbank or a GFF file.

record_class The graphic record class to use, e.g. GraphicRecord (default) or CircularGraphicRecord. Strings 'circular' and 'linear' can also be provided.

filetype Used only when a Genbank or a GFF file is provided; one of "genbank" or "gff" to be used. Default None infers from file extension.

View Source
    def translate_record(self, record, record_class=None, filetype=None):

        """Create a new GraphicRecord from a BioPython Record object.

        Parameters

        ----------

        record

          A BioPython Record object or the path to a Genbank or a GFF file.

        record_class

          The graphic record class to use, e.g. GraphicRecord (default) or

          CircularGraphicRecord. Strings 'circular' and 'linear' can also be

          provided.

        filetype

          Used only when a Genbank or a GFF file is provided; one of "genbank"

          or "gff" to be used. Default None infers from file extension.

        """

        classes = {

            "linear": GraphicRecord,

            "circular": CircularGraphicRecord,

            None: GraphicRecord,

        }

        if record_class in classes:

            record_class = classes[record_class]

        if isinstance(record, str) or hasattr(record, "read"):

            record = load_record(record, filetype=filetype)

        filtered_features = self.compute_filtered_features(record.features)

        return record_class(

            sequence_length=len(record),

            sequence=str(record.seq),

            features=[

                self.translate_feature(feature)

                for feature in filtered_features

                if feature.location is not None

            ],

            **self.graphic_record_parameters

        )