Giter Club home page Giter Club logo

gfapy's Introduction

  • 👋 Hi, I’m @ggonnella
  • 👀 I’m interested in genomics, bioinformatics, strings algorithms, DNA sequences and more.
  • 🐍 I am the author of several open source Python packages, which can be installed using 'pip'.
  • 🌱 I’m currently learning Rust.
  • 💞️ I’m looking to collaborate on interesting life science-related software projects.
  • 📫 How to reach me in LinkedIn: https://www.linkedin.com/in/giorgio-gonnella-36677620/

gfapy's People

Contributors

jwilk avatar satta avatar sjackman avatar ujjwalsh avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

gfapy's Issues

Merging on linear paths errors

Hello,

I think I have found an error during the merging of linear paths with the command line "gfapy-mergelinear"

I reproduced the error with this small example. It contains wrong overlaps but I think it is not linked to the actual problem.

S	A	TTTTTCCCCCGGGGGGGGGGNCCCCCCCCCCTTTTTAAAAA	LN:i:41
S	B	AAAAAAAAAAGGGGGGGGGGNCCCCCCCCCCGGGGGAAAAA	LN:i:41
S	C	CCCCCCCCCCGGGGGGGGGGNCCCCCCCCCCTTTTTTTTTT	LN:i:41
S	D	TTTTTAAAAACCCCCCCCCCNAAAAAAAAAAAAAAATTTTT	LN:i:41
S	E	TTTTTAAAAAGGGGGGGGGGNCCCCCCCCCCCCCCCGGGGG	LN:i:41
S	F	AAAAAAAAAACCCCCCCCCCNAAAAAAAAAAAAAAATTTTT	LN:i:41
S	X1	AAAAATTTTT	LN:i:10
S	X2	AAAAATTTTT	LN:i:10
S	X3	AAAAATTTTT	LN:i:10
S	X4	AAAAATTTTT	LN:i:10
S	X5	AAAAATTTTT	LN:i:10
S	X6	AAAAATTTTT	LN:i:10
S	X7	AAAAATTTTT	LN:i:10
S	X8	AAAAATTTTT	LN:i:10
L	B	+	A	-	5M
L	B	-	C	-	5M
L	C	-	D	-	5M
L	D	-	E	-	5M
L	E	-	F	+	5M
L	A	-	X1	-	5M
L	A	-	X2	+	5M
L	X3	+	A	+	5M
L	X4	-	A	+	5M
L	F	+	X5	-	5M
L	F	+	X6	+	5M
L	X7	+	F	-	5M
L	X8	-	F	-	5M

When I merge this GFA I would expect something like the following merge:

S	A_B_C_D_E_F	TTTTTCCCCCGGGGGGGGGGNCCCCCCCCCCTTTTTAAAAACCCCCGGGGGGGGGGNCCCCCCCCCCTTTTTTTTTTAAAAAGGGGGGGGGGNCCCCCCCCCCGGGGGGGGGGTTTTTTTTTTTTTTTNGGGGGGGGGGTTTTTAAAAAGGGGGGGGGGGGGGGNCCCCCCCCCCTTTTTAAAAAAAAAACCCCCCCCCCNAAAAAAAAAAAAAAATTTTT	LN:i:221	RC:i:221
S	X1	AAAAATTTTT	LN:i:10	RC:i:10
S	X2	AAAAATTTTT	LN:i:10	RC:i:10
S	X3	AAAAATTTTT	LN:i:10	RC:i:10
S	X4	AAAAATTTTT	LN:i:10	RC:i:10
S	X5	AAAAATTTTT	LN:i:10	RC:i:10
S	X6	AAAAATTTTT	LN:i:10	RC:i:10
S	X7	AAAAATTTTT	LN:i:10	RC:i:10
S	X8	AAAAATTTTT	LN:i:10	RC:i:10
L	A_B_C_D_E_F	+	X6	+	5M
L	A_B_C_D_E_F	+	X8	+	5M
L	X1	+	A_B_C_D_E_F	+	5M
L	X2	-	A_B_C_D_E_F	+	5M
L	X3	+	A_B_C_D_E_F	+	5M
L	X4	-	A_B_C_D_E_F	+	5M
L	X5	+	A_B_C_D_E_F	-	5M
L	X7	+	A_B_C_D_E_F	-	5M

Instead I obtain this:

S	X1	AAAAATTTTT	LN:i:10
S	X2	AAAAATTTTT	LN:i:10
S	X3	AAAAATTTTT	LN:i:10
S	X4	AAAAATTTTT	LN:i:10
S	X5	AAAAATTTTT	LN:i:10
S	X6	AAAAATTTTT	LN:i:10
S	X7	AAAAATTTTT	LN:i:10
S	X8	AAAAATTTTT	LN:i:10
S	A_B_C_D_E_F	TTTTTCCCCCGGGGGGGGGGNCCCCCCCCCCTTTTTAAAAACCCCCGGGGGGGGGGNCCCCCCCCCCTTTTTTTTTTAAAAAGGGGGGGGGGNCCCCCCCCCCGGGGGGGGGGTTTTTTTTTTTTTTTNGGGGGGGGGGTTTTTAAAAAGGGGGGGGGGGGGGGNCCCCCCCCCCTTTTTAAAAAAAAAACCCCCCCCCCNAAAAAAAAAAAAAAATTTTT	LN:i:221
L	X4	-	A	+	5M
L	X8	-	F	-	5M
L	A_B_C_D_E_F	-	X1	-	5M
L	X3	+	A_B_C_D_E_F	+	5M
L	A_B_C_D_E_F	+	X5	-	5M
L	X7	+	A_B_C_D_E_F	-	5M

I guess when it re-attaches links between X4 and A or X8 and F, it uses the wrong segment name. This causes errors when processing the GFA after using gfapy to merge. Maybe I am missing something and the input GFA is the problem but I cannot see why.

Cheers,

Antoine

Setting 'or' tag in merge_linear_paths

Hi Giorgio,
Thanks for the great library!
I came across an issue when using merge_linear_paths with enable_tracking=True. Not sure if it's a general problem or maybe a version incompatibility? The 'or' tag of the merged segments is correctly created, but at the end of merging (within __create_merged_segment, line 329 overwriting the 'or' tag by joining the list causes an error with a traceback that I don't understand. If the tag is instead assigned to a new name it works as expected. If it's helpful I attached a gfa file with a single overlap that causes the error for me, but I suspect any would do in my case.
Any ideas what the problem is?
Thanks!

Traceback (most recent call last):
  File "XXX/lib/python3.9/site-packages/gfapy/field/json.py", line 18, in validate_encoded
    json.loads(string)
  File "XXX/lib/python3.9/json/__init__.py", line 346, in loads
    return _default_decoder.decode(s)
  File "XXX/lib/python3.9/json/decoder.py", line 340, in decode
    raise JSONDecodeError("Extra data", s, end)
json.decoder.JSONDecodeError: Extra data: line 1 column 3 (char 2)
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
  File "XXX/lib/python3.9/site-packages/gfapy/field/writer.py", line 61, in _to_gfa_field
    return mod.encode(obj)
  File "XXX/lib/python3.9/site-packages/gfapy/field/json.py", line 42, in encode
    validate_encoded(obj)
  File "XXX/lib/python3.9/site-packages/gfapy/field/json.py", line 20, in validate_encoded
    raise Exception(
AttributeError: 'Exception' object has no attribute 'format'
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
  File "XXX/lib/python3.9/site-packages/gfapy/line/common/writer.py", line 58, in to_list
    fstr = self.field_to_s(fn, tag = True)
  File "XXX/lib/python3.9/site-packages/gfapy/line/common/writer.py", line 103, in field_to_s
    return gfapy.Field._to_gfa_tag(v, fieldname, datatype = t, line = self)
  File "XXX/lib/python3.9/site-packages/gfapy/field/writer.py", line 93, in _to_gfa_tag
    Writer._to_gfa_field(obj, datatype = datatype,
  File "XXX/lib/python3.9/site-packages/gfapy/field/writer.py", line 68, in _to_gfa_field
    raise err.__class__(
AttributeError: Field: or
Datatype: J
Content: '43a47abf-4c9c-470e-833b-b01524a996eb,aa603d54-b794-4fc8-a0f3-ea22497181b3'
'Exception' object has no attribute 'format'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
  File "XXX/lib/python3.9/site-packages/gfapy/field/parser.py", line 61, in _parse_gfa_field
    return mod.decode(string)
  File "XXX/lib/python3.9/site-packages/gfapy/field/json.py", line 10, in decode
    return unsafe_decode(string)
  File "XXX/lib/python3.9/site-packages/gfapy/field/json.py", line 6, in unsafe_decode
    return json.loads(string)
  File "XXX/lib/python3.9/json/__init__.py", line 346, in loads
    return _default_decoder.decode(s)
  File "XXX/lib/python3.9/json/decoder.py", line 340, in decode
    raise JSONDecodeError("Extra data", s, end)
json.decoder.JSONDecodeError: Extra data: line 1 column 3 (char 2)
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
  File "XXX/lib/python3.9/site-packages/gfapy/gfa.py", line 246, in to_file
    f.write(str(line)+"\n")
  File "XXX/lib/python3.9/site-packages/gfapy/line/segment/writer_wo_sequence.py", line 16, in __str__
    return super().__str__()
  File "XXX/lib/python3.9/site-packages/gfapy/line/common/writer.py", line 12, in __str__
    return gfapy.Line.SEPARATOR.join(self.to_list())
  File "XXX/lib/python3.9/site-packages/gfapy/line/common/writer.py", line 60, in to_list
    fstr = str(self.get(fn))
  File "XXX/lib/python3.9/site-packages/gfapy/line/common/field_data.py", line 105, in get
    self._data[fieldname] = gfapy.Field._parse_gfa_field(v, t,
  File "XXX/lib/python3.9/site-packages/gfapy/field/parser.py", line 78, in _parse_gfa_field
    raise err.__class__(
TypeError: __init__() missing 2 required positional arguments: 'doc' and 'pos'

test.gfa.txt

Fill in the sequences in GFA from FASTA

I have a GFA file produce by Canu that has * in the sequences. It also produces a corresponding FASTA file. Is it possible with gfapy to fill in the sequences in the GFA file with the sequences in the FASTA file?

ImportError: cannot import name '_validate_gfa_field'

Steps to reproduce

brew install python3
pip3 install gfapy
gfapy --version
❯❯❯ gfapy-convert --version
Traceback (most recent call last):
  File "/Users/sjackman/.homebrew/bin/gfapy-convert", line 8, in <module>
    from gfapy import Gfa
  File "/usr/local/Cellar/python3/3.6.1/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/gfapy/__init__.py", line 15, in <module>
    from gfapy.field import Field
  File "/usr/local/Cellar/python3/3.6.1/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/gfapy/field/__init__.py", line 4, in <module>
    from .field import Field
  File "/usr/local/Cellar/python3/3.6.1/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/gfapy/field/field.py", line 32, in <module>
    class Field:
  File "/usr/local/Cellar/python3/3.6.1/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/gfapy/field/field.py", line 189, in Field
    from .validator import _validate_gfa_field
ImportError: cannot import name '_validate_gfa_field'

gfapy-validate: spurious warning (GFA 1)

The following gfapy-validate warning seems spurious.

S	A	AAAAAAACGT
S	X	ACGTCCACGT
S	B	ACGTGGGGGG
L	A	+	X	+	4M
L	X	+	B	+	4M
P	P1	A+,X+,B+	4M,4M,4M
gfapy-validate path.gfa
A link equivalent to:
L	B	+	A	+	4M
does not exist, but is required by the following paths:
P	P1	A+,X+,B+	4M,4M,4M

'edge_1+' is not a valid list of GFA1 segment names and orientations

Thanks for the great tool! Running into an issue when trying to load a
GFA straight from a Flye assembly using gfapy 1.2.0. It looks like maybe it doesn't like the paths? Thanks for your help!

 python3 -c "import gfapy; gfa = gfapy.Gfa.from_file('./assembly_graph.gfa'); print(length(gfa.lines))"
Traceback (most recent call last):
  File "/usr/local/lib/python3.10/site-packages/gfapy/field/parser.py", line 61, in _parse_gfa_field
    return mod.decode(string)
  File "/usr/local/lib/python3.10/site-packages/gfapy/field/oriented_identifier_list_gfa1.py", line 9, in decode
    validate_encoded(string)
  File "/usr/local/lib/python3.10/site-packages/gfapy/field/oriented_identifier_list_gfa1.py", line 14, in validate_encoded
    raise gfapy.FormatError(
gfapy.error.FormatError: 'edge_1+' is not a valid list of GFA1 segment names and orientations
(the segment names must match [!-)+-<>-~][!-~]*;
 the orientations must be + or -;
 the list must be comma-separated NameOrient,NameOrient[,NameOrient...])

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/usr/local/lib/python3.10/site-packages/gfapy/gfa.py", line 234, in from_file
    gfa.read_file(filename)
  File "/usr/local/lib/python3.10/site-packages/gfapy/gfa.py", line 208, in read_file
    self.add_line(line.rstrip('\r\n'))
  File "/usr/local/lib/python3.10/site-packages/gfapy/lines/creators.py", line 25, in add_line
    self.__add_line_GFA1(gfa_line)
  File "/usr/local/lib/python3.10/site-packages/gfapy/lines/creators.py", line 134, in __add_line_GFA1
    gfa_line = gfapy.Line(gfa_line, vlevel=self._vlevel,
  File "/usr/local/lib/python3.10/site-packages/gfapy/line/common/construction.py", line 79, in __init__
    self._initialize_positional_fields(data)
  File "/usr/local/lib/python3.10/site-packages/gfapy/line/common/construction.py", line 160, in _initialize_positional_fields
    self._init_field_value(n, self.__class__.DATATYPE[n], strings[i+1],
  File "/usr/local/lib/python3.10/site-packages/gfapy/line/common/construction.py", line 139, in _init_field_value
    s = gfapy.Field._parse_gfa_field(s, t, safe = True, fieldname = n,
  File "/usr/local/lib/python3.10/site-packages/gfapy/field/parser.py", line 78, in _parse_gfa_field
    raise err.__class__(
gfapy.error.FormatError: Field: segment_names
Datatype: oriented_identifier_list_gfa1
Content: edge_1+
'edge_1+' is not a valid list of GFA1 segment names and orientations
(the segment names must match [!-)+-<>-~][!-~]*;
 the orientations must be + or -;
 the list must be comma-separated NameOrient,NameOrient[,NameOrient...])

Missing coverage information after merge_linear_paths()

Is it possible to keep coverage/count information when using merge_linear_paths?

E.g., this is snippet of my starting graph which has FC tags:

S	35	TATTTTTATTAGAGACCTTGATTCCGATAATATTCATGTTGTATTAGCCGATTTTGGTCTAGCCAAAGCAAGTATGGGTTCAGTTGACAGATCCTATGCTGGCACTCCTTTATACATGTCTTTAGAACTCGCAACCGGAGGAAAGTACAGTTTCAATACCGATATCTATTCATTGGGAGTGGCTATCTATCAAATCATGACCAAAGACACAACCACAGCTGTCAGTCACATGTTTATTTCGAATAGAGAGGAAGAAGCAATTGATAAATTAACAAAGGAAATGGAAAAATCAGGAAATTATTCAACAACATTGATTAATATTTGTTTATCTATGCTGGAGAAGGATGCCAGTAAGAGACCAAGTGCCAAGCAAATCCTTTCCAATACTTTCTTGGAAGATATTCTCATCACACCAACAGAATAAACAATTTCTTGTGTTTTACCGCTGAAGTTAACCGTTAGAATTATAATAATTTTATTTTTCACAACAAAAAAAACAAGCCAACAACAATAGAGCGGGTGCAAACTCGGGTGTATCTACCGTTTTTCGTTTCTGAAAACGACTTTCGTTTTTGTTCGTGTTTGAATGAAGATTTTTGGTGTTTCTTTCAAATTCATACAAGATCGTTTTTCGTTTTTGTAAAAAGTTAGAAAAATACGAAAACGTGGCAAGTTGCACCTGCTCTAATGTGGGCAACAAACTTGAGGTTACCATTACCTTCTTGAATGATCATCAGAAGATTATTCATCTTCGCTTACCATCAGAGATCTGATTATTAACCGCTCTTGACTGTCAACTGAGATTTGGCAGGTGAAATTCAGTTTTCACATTCGCCAAAAGAAAATGAAGATAAATGATGATGAATTGAATAATTCTTGAATTTCTTCCTACGGATAGCTATGCATCATTCTCACATATCAATAGGCAGTGTCAATCTATTCTTGTTTCGGAAAAACCAAACTTTTGGAAACAGAAAAAGAGTGAATATGATAGAACTATTGCCAACCAAGAACGATACCTCGACCATGTTATTGATACAATGACAAGAAATGCTTCTAAGAACAATTATTGTTTATTTGCTTGTTTTGTCGGGATGTTGTTTTCGTTTCTTGTGTTTTTACCAGCATTTATGATCATGGTGATTATGGCATCTAGTTCCCTTTCAGGAAATATATTGAATTGTGAGAGGAATGTTTCTTCTATTCAAGTTTTGAATAGAACTATTGAAAGGGTTTCTTATCCAGGATGTGTAGAATGTAACACAATTTGGAATACTAGTTGCAATAATTTCAATGTTATGCCATGTTGTCAACCAAATAATAGTTATTCTTCAATTTTCTCATATGAATGCAATGTAACTGAATGCATTAAGGAAAATTATCAAGATGGACCTCTCAGATGTCGAGTAATTAGTTACAACAACAAAAAAGGGCCATCCACATATTCACTAGTCATTTACAAACCAGAAAGCAATTATTCAGGGTCTATAGGACTATTATCTGGTTAGCTACCAATACTTATGATTTGAAAGTTTAGTCAGATTACTGAAATACTGACTTTATTTCACTAACAACACGTAGGTCGGCAATGTGATTCCTTCTTCATAATTGAGTATTAATCGTACGGTATTTGCAAGTAAATAATAACTAATAATGGTCAATTAACAAAATATTAAATAAATTATTTAAAAAAAATGAAAAGAAGCAGAGAGGAGGAACCTGCTGTTACCAATACATCATGTACTGATCAAAGTAATACAAGTTCCGATCCTCTCTTGGAAACGAATCTAAAAAAACAAGTGACTGGCAATATTT	ll:f:44.5	FC:i:80768	kl:f:44.5
S	11508	ACTGTCAACTGAGATTTGGCAGGTGAAATTCAGTTTTCACATTCGCCAAAAGAAAATGAAGATAAATGATGATGAATTGAATAATTCTTGAATTTCTTCCTACGGATAGCTATGCATCATTCTCACATATCAATAGGCAGTGTCAATCTATTCTTGTTTCGGAAAAACCAAACTTTTGGAAACAGAAAAAGAGTGAATATGATAGAACTATTGCCAACCAAGAACGATACCTCGACCATGTTATTGATACAATGACAAGAAATGCTTCTAAGAACAATTATTGTTTATTTGCTTGTTTTGTCGGGATGTTGTTTTCGTTTCTTGTGTTTTTACCAGCATTTATGATCATGGTGATTATGGCATCTAGTTCCCTTTCAGGAAATATATTGAATTGTGAGAGGAATGTTTCTTCTATTCAAGTTTTGAATAGAACTATTGAAAGGGTTTCTTATCCAGGATGTGTAGAATGTAACACAATTTGGAATACTAGTTGCAATAATTTCAATGTTATGCCATGTTGTCAACCAAATAATAGTTATTCTTCAATTTTCTCATATGAATGCAATGTAACTGAATGCATTAAGGAAAATTATCAAGATGGACCTCTCAGATGTCGAGTAATTAGTTACAACAACAAAAAAGGGCCATCCACATATTCACTAGTCATTTACAAACCAGAAAGCAATTATTCAGGGTCTATAGGACTATTATCTGGTTAGCTACCAATACTTATGATTTGAAAGTTTAGTCAGATTACTGAAATACTGACTTTATTTCACTAACAACACGTAGGTCGGCAATGTGATTCCTTCTTCATAATTGAGTATTAATCGTACGGTATTTGCAAGTAAATAATAACTAATAATGGTCAATTAACAAAATATTAAATAAATTATTTAAAAAAAATGAAAAGAAGCAGAGAGGAGGAACCTGCTGTTACCAATACATCATGTACTGATCAAAGTAATACAAGTTCCGATCCTCTCTTGGAAACGAATCTAAAAAAACAAGTGACTGGCAATATTTCTAGTGAATCATCGTATTTTAGTCTTGGTTGTAATTATATGGATGGTGTTAATGGTGTTGAAAAAGATTATTTAAAAGCATTTGAATTATTTTCTAAAGGTGTTGAGGAGGGATGCTTGCGATCAATACATAAAATTGGTTACTTTTATAGCAAAGGATTGGGAGTTGAACAAGATTATGATAAAGCAGTCCAGTTCTATATGAAATCTGCTGAAGGGGGTTTTGTGGCCTCATATGCTAAGGGAAAGGCGTTCAACAGGACCATGTTAAAGCATTTGAATGGACTTTAAAAGCTGCTGAAAATGATAATACTGATGCACAATTTAACATTGGAAGATGCTATAGTAAAGGGACAGGAGTTGAGCAAGACCATGTTAAAGCATTTGAATGGTATTTAAAAGCTGCTGAAAAGGGTGATACAGATGCACAGTTTGTGATTGGA	ll:f:43	FC:i:63124	kl:f:43

But when I run graph.merge_linear_paths() the FC tag is lost:

S	35_11508	TATTTTTATTAGAGACCTTGATTCCGATAATATTCATGTTGTATTAGCCGATTTTGGTCTAGCCAAAGCAAGTATGGGTTCAGTTGACAGATCCTATGCTGGCACTCCTTTATACATGTCTTTAGAACTCGCAACCGGAGGAAAGTACAGTTTCAATACCGATATCTATTCATTGGGAGTGGCTATCTATCAAATCATGACCAAAGACACAACCACAGCTGTCAGTCACATGTTTATTTCGAATAGAGAGGAAGAAGCAATTGATAAATTAACAAAGGAAATGGAAAAATCAGGAAATTATTCAACAACATTGATTAATATTTGTTTATCTATGCTGGAGAAGGATGCCAGTAAGAGACCAAGTGCCAAGCAAATCCTTTCCAATACTTTCTTGGAAGATATTCTCATCACACCAACAGAATAAACAATTTCTTGTGTTTTACCGCTGAAGTTAACCGTTAGAATTATAATAATTTTATTTTTCACAACAAAAAAAACAAGCCAACAACAATAGAGCGGGTGCAAACTCGGGTGTATCTACCGTTTTTCGTTTCTGAAAACGACTTTCGTTTTTGTTCGTGTTTGAATGAAGATTTTTGGTGTTTCTTTCAAATTCATACAAGATCGTTTTTCGTTTTTGTAAAAAGTTAGAAAAATACGAAAACGTGGCAAGTTGCACCTGCTCTAATGTGGGCAACAAACTTGAGGTTACCATTACCTTCTTGAATGATCATCAGAAGATTATTCATCTTCGCTTACCATCAGAGATCTGATTATTAACCGCTCTTGACTGTCAACTGAGATTTGGCAGGTGAAATTCAGTTTTCACATTCGCCAAAAGAAAATGAAGATAAATGATGATGAATTGAATAATTCTTGAATTTCTTCCTACGGATAGCTATGCATCATTCTCACATATCAATAGGCAGTGTCAATCTATTCTTGTTTCGGAAAAACCAAACTTTTGGAAACAGAAAAAGAGTGAATATGATAGAACTATTGCCAACCAAGAACGATACCTCGACCATGTTATTGATACAATGACAAGAAATGCTTCTAAGAACAATTATTGTTTATTTGCTTGTTTTGTCGGGATGTTGTTTTCGTTTCTTGTGTTTTTACCAGCATTTATGATCATGGTGATTATGGCATCTAGTTCCCTTTCAGGAAATATATTGAATTGTGAGAGGAATGTTTCTTCTATTCAAGTTTTGAATAGAACTATTGAAAGGGTTTCTTATCCAGGATGTGTAGAATGTAACACAATTTGGAATACTAGTTGCAATAATTTCAATGTTATGCCATGTTGTCAACCAAATAATAGTTATTCTTCAATTTTCTCATATGAATGCAATGTAACTGAATGCATTAAGGAAAATTATCAAGATGGACCTCTCAGATGTCGAGTAATTAGTTACAACAACAAAAAAGGGCCATCCACATATTCACTAGTCATTTACAAACCAGAAAGCAATTATTCAGGGTCTATAGGACTATTATCTGGTTAGCTACCAATACTTATGATTTGAAAGTTTAGTCAGATTACTGAAATACTGACTTTATTTCACTAACAACACGTAGGTCGGCAATGTGATTCCTTCTTCATAATTGAGTATTAATCGTACGGTATTTGCAAGTAAATAATAACTAATAATGGTCAATTAACAAAATATTAAATAAATTATTTAAAAAAAATGAAAAGAAGCAGAGAGGAGGAACCTGCTGTTACCAATACATCATGTACTGATCAAAGTAATACAAGTTCCGATCCTCTCTTGGAAACGAATCTAAAAAAACAAGTGACTGGCAATATTTCTAGTGAATCATCGTATTTTAGTCTTGGTTGTAATTATATGGATGGTGTTAATGGTGTTGAAAAAGATTATTTAAAAGCATTTGAATTATTTTCTAAAGGTGTTGAGGAGGGATGCTTGCGATCAATACATAAAATTGGTTACTTTTATAGCAAAGGATTGGGAGTTGAACAAGATTATGATAAAGCAGTCCAGTTCTATATGAAATCTGCTGAAGGGGGTTTTGTGGCCTCATATGCTAAGGGAAAGGCGTTCAACAGGACCATGTTAAAGCATTTGAATGGACTTTAAAAGCTGCTGAAAATGATAATACTGATGCACAATTTAACATTGGAAGATGCTATAGTAAAGGGACAGGAGTTGAGCAAGACCATGTTAAAGCATTTGAATGGTATTTAAAAGCTGCTGAAAAGGGTGATACAGATGCACAGTTTGTGATTGGA	ll:f:44.5	kl:f:44.5	LN:i:2257

Taking very long to load GFA1 from file

I was trying to load a GFA1 from a file with gfapy but I had to kill the process because it is taking over 15 minutes and not finishing. I am not sure what could be wrong.

The GFA is a de Bruijn graph and is the output of convertToGFA.py , where this script converts the contigs from bcalm2 output to a valid GFA1 file.
This graph has 944785 nodes and 2419232 edges.

Minimal example here:

import gfapy
import time

input_file = "sk1_y12_yeast_k43.gfa"

start = time.perf_counter()
graph = gfapy.Gfa.from_file(input_file)
print(f"it took {time.perf_counter() - start} seconds to load the file")

Is it supposed to take this long?

Feature: converting graphs to GFA

Thank you for your work on gfapy;

In the documentation, I could not find a function to convert well-known graph data structures like igraph or networkx to gfa. Assume that we worked on a graph in Python and then we want to print it to a gfa file. One way is to iterate over the graph and add lines to a gfa instance, but it would probably be very useful and more convenient to have this implemented within gfapy.

Also, in a more general way, would be nice if we could have access to functions like add_graph_node() or add_graph_edge, that add the lines for nodes and edges without the user spelling the full line using add_line

Calling .complement() on GFA2 edges

Hi, and thanks for developing gfapy!

I've been working with GFA1 and GFA2 files (in particular, sample1.gfa and sample2.gfa in this repository—these specify the same general graph, but in GFA1 and GFA2 respectively). Something I've noticed is that edges in GFA1 graphs have a .complement() method, which returns an edge in the opposite direction:

>>> g1 = gfapy.Gfa.from_file("sample1.gfa")
>>> g1.edges[0]
gfapy.Line('L	1	+	2	+	5M',version='gfa1',vlevel=1)
>>> g1.edges[0].complement()                                                     
gfapy.Line('L	2	-	1	-	5M',version='gfa1',vlevel=1)

This is a really useful feature. However, it looks like this isn't available for GFA2 edges:

>>> g2 = gfapy.Gfa.from_file("sample2.gfa")
>>> g2.edges[0]
gfapy.Line('E	*	1+	2+	3	8$	0	5	0,2,TS:i:2',version='gfa2',vlevel=1)
>>> g2.edges[0].complement()                                                     
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-9-e937829f2032> in <module>
----> 1 g2.edges[0].complement()

/anaconda3/envs/metagenomescope/lib/python3.6/site-packages/gfapy/line/common/dynamic_fields.py in __getattribute__(self, name)
     23         return attr.get(self)
     24     except AttributeError as err:
---> 25       return self._get_dynamic_field(name, err)
     26 
     27   def __setattr__(self, name, value):

/anaconda3/envs/metagenomescope/lib/python3.6/site-packages/gfapy/line/common/dynamic_fields.py in _get_dynamic_field(self, name, err)
     53           "No value defined for tag {}".format(name))
     54     else:
---> 55       raise err
     56 
     57   def _set_dynamic_field(self, name, value):

/anaconda3/envs/metagenomescope/lib/python3.6/site-packages/gfapy/line/common/dynamic_fields.py in __getattribute__(self, name)
     17   def __getattribute__(self, name):
     18     try:
---> 19       attr = super().__getattribute__(name)
     20       if not isinstance(attr, DynamicField):
     21         return attr

AttributeError: 'GFA2' object has no attribute 'complement'

Is this an intentional decision? I haven't been able to find much information about the .complement() method throughout the gfapy documentation, so I can't tell if this is a bug or by design.

Thank you again for developing this project!

gfapy.error.FormatError: Custom tags must be lower case

I have a gfa file, which look as follows:

S utg000001l *
S utg000002l *
S utg000003l *
S utg000004l *
S utg000005l *
[...]
L utg000001l + utg058900l + 12383M L1:i:913617
L utg000001l + utg062906l - 12217M L1:i:913783
L utg000001l - utg028945l - 13052M L1:i:912948
L utg000002l + utg043590l + 10450M L1:i:587175
L utg000002l - utg044206l - 18954M L1:i:578671
[...]

When parsing the file I am running into "gfapy.error.FormatError: Custom tags must be lower case".
According to the gfa specification, L1:i should be fine?

linear_path error

Dear Giorgio,

I am trying to implement a sub-graph traversal functon using some of the methods in the GraphOperations class. I couldn't find much on this in the online documentation, so I'm trying this the hard way :) The linear_path() method, if I understand it correctly, expects a segment from the graph, but since this is not a method of the Gfa class, I can't call it on the graph object itself. I'm assuming that internally, the recursive _traverse() function somehow follows references to go from one segment to the next without having to access the gfa object itself. But the way I'm calling the method must be wrong as it fails to accept the segment argument. Here's how I'm calling it.

[ ... after loading a GFA1 file into a Gfa object: ]
from gfapy.graph_operations import GraphOperations
path=graphOperations.linear_path(gfaOne.segments[5], None)

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-73-15bd5f07fa1b> in <module>
----> 1 GraphOperations.linear_path(gfaOne.segments[5], None)

~/.conda/envs/panG/lib/python3.8/site-packages/gfapy/graph_operations/linear_paths.py in linear_path(self, segment, exclude)
     14     else:
     15       segment_name = segment
---> 16       segment = self.segment(segment_name)
     17     cs = segment._connectivity()
     18     if exclude is None:

~/.conda/envs/panG/lib/python3.8/site-packages/gfapy/line/common/dynamic_fields.py in __getattribute__(self, name)
     23         return attr.get(self)
     24     except AttributeError as err:
---> 25       return self._get_dynamic_field(name, err)
     26 
     27   def __setattr__(self, name, value):

~/.conda/envs/panG/lib/python3.8/site-packages/gfapy/line/common/dynamic_fields.py in _get_dynamic_field(self, name, err)
     53           "No value defined for tag {}".format(name))
     54     else:
---> 55       raise err
     56 
     57   def _set_dynamic_field(self, name, value):

~/.conda/envs/panG/lib/python3.8/site-packages/gfapy/line/common/dynamic_fields.py in __getattribute__(self, name)
     17   def __getattribute__(self, name):
     18     try:
---> 19       attr = super().__getattribute__(name)
     20       if not isinstance(attr, DynamicField):
     21         return attr

AttributeError: 'GFA1' object has no attribute 'segment'

What is the correct way of using this method?

Thanks!

conversion between GFA and GFA2 leaves out path tags

Hi,
When using the to_gfa2 and to_gfa1 methods (same for to_gfa*_s), any tags in the path lines, whether ordered or unordered, are ignored and not transferred to the new object. Since tags are generally legal to all lines in both GFA and GFA2 (please correct me if I'm wrong here), this should probably be fixes.

Thanks

Merging linear paths : computed sequence length and computed LN differ

Hello,

I am trying to merge linear paths in a GFA after removing some segments (and their associated links).

However, I got this error : 'str' object has no attribute 'length'

After some looking into it, I think the error is not the actual error. It is due to this condition in line 335 of the linear_paths.py file merged.LN != len(merged.sequence):

Since merged.LN and len(merged.sequence) were different, it should raise the InconsistencyError but it raises the aforementioned "no length attribute error" instead. I think it is because in the lines 336-338, the format call for merged.sequence.length instead of len(merged.sequence) :

"""Currently : raises Error 'str' object has no attribute 'length'"""

        elif self._vlevel > 0 and merged.LN != len(merged.sequence):
          raise gfapy.InconsistencyError(
              "Computed sequence length {} ".format(merged.sequence.length)+
              "and computed LN {} differ".format(merged.LN))

"""Correction, that now raises : Error Computed sequence length 15968 and computed LN 15386 differ"""

        elif self._vlevel > 0 and merged.LN != len(merged.sequence):
          raise gfapy.InconsistencyError(
              "Computed sequence length {} ".format(len(merged.sequence))+
              "and computed LN {} differ".format(merged.LN))

Now, when I try, I get the error : Computed sequence length 15968 and computed LN 15386 differ. Do you know why the merging creates this difference (here 582 bp) and how to avoid it (besides putting the vlevel to 0) ?

I do not know if this is relevant but, I get this error only with some GFA produced by the Raven assembler whereas with the same input reads but using other assemblers (such as miniasm or Flye), the merging works fine.

EDIT:

After looking at this issue further, I found that it happens in cases such as the example below. Two segments are linked but the CIGAR ni the link has a longer match than the length of one of the two linked sequences. Here : 7M > length of SegB

S       SegA    AAAAAAAAAA      LN:i:10 RC:i:1
S       SegB    TTTTT   LN:i:5  RC:i:1
L       SegA    +       SegB    -       7M

The following code:

import gfapy
import sys
gfa = gfapy.Gfa.from_file(sys.argv[1])
newgfa = gfa.merge_linear_paths()
[0] gfapy.error.InconsistencyError: Computed sequence length 10 and computed LN 8 differ

Interestingly, when using gfapy-mergelinear, the error is not raised and the merged GFA contains one segment with LN:i:8 when the reported sequence is actually 10 base long. I think this is because the vlevel is set to 0 when using gfapy-mergelinear whereas is was set to default (I guess vlevel=1) in the above python script.

S       1       AAAAAAAAAA      LN:i:8

I do not know which behavior is expected in such cases.

Cheers

gfapy-convert: has no identifier Path conversion to GFA2 failed

gfapy-convert requires custom id:Z tags to convert from GFA1 to GFA2, which means it's not possible to convert a typical GFA 1 file with paths that does not have such IDs. GFA 2 does not explicitly required edges IDs when the paths are unambiguous, which is the case for GFA 1.

Alternatively, the edges could be numbered sequentially.

S	A	AAAAAAACGT
S	B	ACGTCCACGT
L	A	+	B	+	4M
P	P1	A+,B+	4M
gfapy-convert path.gfa
Link L	A	+	B	+	4M has no identifier
Path conversion to GFA2 failed

Expected output

S	A	10	AAAAAAACGT
S	B	10	ACGTCCACGT
E	*	A+	B+	6	10$	0	4	4M
O	P1	A+ B+

Alternative output

S	A	10	AAAAAAACGT
S	B	10	ACGTCCACGT
E	1	A+	B+	6	10$	0	4	4M
O	P1	A+ 1+ B+

python3 set.append not allowed

Hi Giorgio,
It looks like some of the methods in graph_opreations/ are using append() with sets, which Python3 is not happy about :/

gfaOne.linear_paths(gfaOne.segments[3])

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-37-f612fbc3a014> in <module>
----> 1 gfaOne.linear_paths(gfaOne.segments[3])

~/.conda/envs/panG/lib/python3.8/site-packages/gfapy/graph_operations/linear_paths.py in linear_paths(self, redundant_junctions)
     58           retval.append(lp)
     59         else:
---> 60           retval += self._junction_junction_paths(sn, junction_exclude)
     61     if self._progress:
     62       self._progress_log_end("linear_paths")

~/.conda/envs/panG/lib/python3.8/site-packages/gfapy/graph_operations/redundant_linear_paths.py in _junction_junction_paths(self, sn, exclude)
      5   def _junction_junction_paths(self, sn, exclude):
      6     retval = []
----> 7     exclude.append(sn)
      8     s = self.segment(sn)
      9     for dL in s.dovetails_L:

AttributeError: 'set' object has no attribute 'append'

Minimum Python version

pip install gfapy with Python 2.x works, but then running the program gives the error:

❯❯❯ gfapy-convert --help
Traceback (most recent call last):
  File "/Users/sjackman/.homebrew/bin/gfapy-convert", line 8, in <module>
    from gfapy import Gfa
  File "/usr/local/Cellar/python/2.7.13/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/gfapy/__init__.py", line 6, in <module>
    from gfapy.field_array import FieldArray
  File "/usr/local/Cellar/python/2.7.13/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/gfapy/field_array.py", line 30
    def validate(self, fieldname : str = None) -> None:
                                 ^
SyntaxError: invalid syntax

Is it possible to indicate the minimum Python version in the package so that the user receives a meaningful error message with Python 2.x?

validating paths

Hello,
I'm wondering if gfapy includes methods for validating paths. It's possible to add a path (ordered or unordered) to a subgraph, where some path elements are missing from the graph, i.e. they're not present as a valid segment or edge. If wholesale path validation doesn't check for this, I could just write my own method that checks every path segment for "existence". I was looking for a convenient boolean test that would just tell me that but didn't find it (or didn't know where to look)

Thanks!

unhashable type: 'SegmentEnd' with `is_cut_segment`

Hello,

I am encountering an error when attempting to use the functions graph.is_cut_segment and graph.remove_dead_ends where it appears that the function attempts to interate over a SegmentEnd and fails:

EG

In [41]: graph.remove_dead_ends(1000000)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-41-da5e655a6017> in <module>()
----> 1 graph.remove_dead_ends(1000000)

/Users/henglinm/opt/miniconda3/envs/tester/lib/python3.6/site-packages/gfapy/graph_operations/artifacts.py in remove_dead_ends(self, minlen)
     34       if s.length < minlen and \
     35         (c[0]==0 or c[1]==0) and \
---> 36           not self.is_cut_segment(s):
     37         self.rm(s)

/Users/henglinm/opt/miniconda3/envs/tester/lib/python3.6/site-packages/gfapy/graph_operations/topology.py in is_cut_segment(self, segment)
     52       for l in segment.dovetails_of_end(et):
     53         start_points.add(l.other_end(\
---> 54             gfapy.SegmentEnd(segment.name, et)).inverted())
     55     cc = []
     56     for start_point in start_points:

TypeError: unhashable type: 'SegmentEnd'

This was performed a version 1 GFA file immediately after import. Is this only for GFA 2 files?

Compact the assembly graph [feature request]

I'd like to compact the assembly graph (merge all possible segments). Bandage has this feature from the GUI, but doesn't expose it to the command line. See https://github.com/rrwick/Bandage/wiki/Editing-graphs#merge-all-possible-nodes

I'm currently interested in this feature for assembly graphs where all the overlap edges are blunt 0M, which makes merging the sequences trivial. This operation is also easy if the overlaps are perfect 1234M, but gets tricky if indels are involved. I'm not currently interested in handling indels.

Installation issue on Ubuntu 20.04

I'm having issues trying to install gfapy on Ubuntu 20.04 LTS. I've tried each installation method but python is not finding it?

Regular

mtg@mtg-ThinkPad-P53:~/projects/TEST-MOMIG$ pip install gfapy

Remove with pip uninstall gfapy then,

Development version

mtg@mtg-ThinkPad-P53:~/projects/TEST-MOMIG$ pip install -e git+https://github.com/ggonnella/gfapy.git#egg=gfapy

Again remove, then:

Conda

mtg@mtg-ThinkPad-P53: conda activate base
(base) mtg@mtg-ThinkPad-P53:~/projects/TEST-MOMIG$ conda install -c bioconda gfapy

After each one I tried the following with the same error:

(base) mtg@mtg-ThinkPad-P53:~/projects/TEST-MOMIG$ python
Python 3.8.2 (default, Apr 27 2020, 15:53:34) 
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import gfapy
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'gfapy'

Any idea what is going on?

gfapy.error.NotFoundError: A link equivalent to ... does not exist

Dear Giorgio

Thanks for your nice library!

I get this exception:

gfapy.error.NotFoundError: A link equivalent to:
L	edge_2	-	edge_1	+	*
does not exist, but is required by the following paths:
P	contig_4	edge_4+,edge_2-,edge_1+,edge_2+	*

When trying this .gfa file produced by Flye:

H	VN:Z:1.0
S	edge_1	GCCAGCCTAAGAAAGACGCAT	dp:i:1384
S	edge_2	CACGAACTGCACCTTTCAATT	dp:i:1370
S	edge_3	TATAGTCATAAATTATTAGTT	dp:i:453
S	edge_4	ACTTAGTGGTTTTGTATATTT	dp:i:858
L	edge_1	+	edge_2	+	0M	RC:i:1288
L	edge_2	+	edge_4	+	0M	RC:i:604
L	edge_2	+	edge_4	-	0M	RC:i:602
L	edge_3	+	edge_3	+	0M	RC:i:0
L	edge_3	-	edge_3	-	0M	RC:i:0
P	contig_3	edge_3+	*
P	contig_4	edge_4+,edge_2-,edge_1+,edge_2+	*

assembly graph (gfaviz)

Is this a Flye or a gfapy bug? (Made the plot using gfaviz which didn't complain.)

Could you perhaps change from an exception to printing a mere warning? This is my ugly monkey patch that allows me to continue working:

image

Optimization: add ability to ignore sequences when loading a GFA file

Thank you for your work on Gfapy!

I have a very large GFA file (over 3 GB), including ~80k segments that I'd like to load using Gfapy. Although this graph contains sequence information for each segment, I don't really care about the segments' sequences—I just want to know the topology of the graph, and the lengths of each segment.

Gfapy takes a very long time to load this file (I'm not sure if it will ever load before running out of memory: I killed it after a few minutes on our cluster). It ended up being easier for me to write a simple [albeit much less robust than Gfapy :) -- i.e. without GFA2 support] line-by-line parser which completely ignores the segments' sequences.

This line-by-line parser takes only a few seconds to run on the aforementioned large GFA file, and I am fairly sure that the reason for this speed-up is that my parser completely ignores segments' sequences (and instead treats each segment line as if it were written as S \t segname \t * \t LN:i:100 or something). I suspect this is also the reason why Gfapy's parser took a long amount of time in issue #23 in this repository; from going through the script that generated the GFA file discussed there, it seems like this script includes sequences in the GFA file, and thus causes Gfapy to spend a lot of time loading these (usually relatively large) sequences into memory.

I am writing some code now to work with arbitrary GFA files, so I'd really like to be able to use Gfapy; however, the long amount of time it takes to load files is a blocker for me. I would therefore like to propose that Gfapy's Gfa.from_file() function add an optional flag parameter to ignore sequences in segment lines. I'm not sure how easy this would be to implement in Gfapy's code, but I think this could really increase the sizes of graphs that Gfapy could work with.

Thanks for your consideration, and thanks again for your work on this project.

Possible bug in captured_path.py

Hi,
I ran into a weird issue where when processing an oriented path from a GFA2 file, captured_path.py throws an exception claiming that the path is invalid:

Traceback (most recent call last):
  File "add_annotation_to_gfa.py", line 159, in <module>
    nFeatAdded = add_features(gfa2, p.captured_segments)
  File "/global/homes/e/eugeneg/.conda/envs/panG/lib/python3.8/site-packages/gfapy/line/common/dynamic_fields.py", line 19, in __getattribute__
    attr = super().__getattribute__(name)
  File "/global/homes/e/eugeneg/.conda/envs/panG/lib/python3.8/site-packages/gfapy/line/group/ordered/captured_path.py", line 7, in captured_segments
    return [ x for x in self.captured_path if isinstance(x.line, gfapy.line.segment.GFA2) ]
  File "/global/homes/e/eugeneg/.conda/envs/panG/lib/python3.8/site-packages/gfapy/line/common/dynamic_fields.py", line 19, in __getattribute__
    attr = super().__getattribute__(name)
  File "/global/homes/e/eugeneg/.conda/envs/panG/lib/python3.8/site-packages/gfapy/line/group/ordered/captured_path.py", line 20, in captured_path
    return self._compute_captured_path()[0]
  File "/global/homes/e/eugeneg/.conda/envs/panG/lib/python3.8/site-packages/gfapy/line/group/ordered/captured_path.py", line 26, in _compute_captured_path
    path, prev_edge = self._push_item_on_se_path(path, prev_edge, item)
  File "/global/homes/e/eugeneg/.conda/envs/panG/lib/python3.8/site-packages/gfapy/line/group/ordered/captured_path.py", line 52, in _push_item_on_se_path
    self._push_nonfirst_edge_on_se_path(path, item)
  File "/global/homes/e/eugeneg/.conda/envs/panG/lib/python3.8/site-packages/gfapy/line/group/ordered/captured_path.py", line 138, in _push_nonfirst_edge_on_se_path
    raise gfapy.NotFoundError(
gfapy.error.NotFoundError: Path is not valid, elements are not contiguous

I checked the path, and the elements are in fact contiguous and a unique edge exists for every pair of path segments. Looking at the code it seems that the culprit is the call to invert the orientation of the spanning segments when adding a "negative" edge:

possible_prev[i].invert()

Somehow, in my case, an edge was being visited twice (not sure how that's possible since all of the path elements are unique), and since the invert() method flips the orientation in place, it would effectively undo itself, i.e. flipping - to + on the first encounter and then - back to + the second time around. This results in the mismatch between prev_os and possible_prev[i] , causing the error.
I was able to get around this by replacing
possible_prev[i].invert()
with
possible_prev[i] = possible_prev[i].inverted()

(inverted() apparently returns a local copy)

I didn't want to make a pull request because I'm not sure I've addressed the root cause, which I think is the fact that there is a second visit to the same edge on a single path. I can see cases where this would be true, e.g. when a path involves a cycle, but I have no such paths in my graph afaik. Perhaps there is a thread locking issue during parallel path walking? Just a wild guess...

Thanks

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.