syncoder 0.0.0__tar.gz → 0.0.1a1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- syncoder-0.0.1a1/.gitignore +17 -0
- syncoder-0.0.1a1/PKG-INFO +39 -0
- syncoder-0.0.1a1/README.md +27 -0
- {syncoder-0.0.0 → syncoder-0.0.1a1/environments}/conda_environment_dev.yml +2 -1
- {syncoder-0.0.0 → syncoder-0.0.1a1}/pyproject.toml +7 -2
- syncoder-0.0.1a1/src/syncoder/__init__.py +4 -0
- {syncoder-0.0.0 → syncoder-0.0.1a1}/src/syncoder/codec.py +54 -29
- {syncoder-0.0.0 → syncoder-0.0.1a1}/tests/test_codec_pytest.py +30 -3
- syncoder-0.0.0/.gitignore +0 -11
- syncoder-0.0.0/PKG-INFO +0 -13
- syncoder-0.0.0/README.md +0 -1
- syncoder-0.0.0/foo.py +0 -4
- syncoder-0.0.0/src/syncoder/__init__.py +0 -1
- {syncoder-0.0.0 → syncoder-0.0.1a1}/.vscode/.gitignore +0 -0
- {syncoder-0.0.0 → syncoder-0.0.1a1}/LICENSE +0 -0
- {syncoder-0.0.0 → syncoder-0.0.1a1/environments}/conda_environment.yml +0 -0
- {syncoder-0.0.0 → syncoder-0.0.1a1/environments}/conda_environment_test.yml +0 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#some temp and build directories
|
|
2
|
+
__pycache__
|
|
3
|
+
dist/*
|
|
4
|
+
Docs/
|
|
5
|
+
data/
|
|
6
|
+
out/
|
|
7
|
+
#ingnore data files that may be used or generated during dev.
|
|
8
|
+
*.ipynb
|
|
9
|
+
*.dna
|
|
10
|
+
*.pdf
|
|
11
|
+
*.txt
|
|
12
|
+
*.json
|
|
13
|
+
*.zip
|
|
14
|
+
*.jpg
|
|
15
|
+
*.bin
|
|
16
|
+
#any actual py files are in src. anything in root is supposed to just for development.
|
|
17
|
+
/*.py
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: syncoder
|
|
3
|
+
Version: 0.0.1a1
|
|
4
|
+
Summary: A codec for DNA data storage
|
|
5
|
+
Project-URL: Homepage, https://example.com/TBD
|
|
6
|
+
Project-URL: Issues, https://example.com/TBD/issues
|
|
7
|
+
Author-email: Christopher Takahashi <cnt@uw.edu>
|
|
8
|
+
License-Expression: BSD-3-Clause
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Python: >=3.9
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
# SyNCODER
|
|
14
|
+
|
|
15
|
+
## Project Overview
|
|
16
|
+
A DNA codec. (Fill in.)
|
|
17
|
+
|
|
18
|
+
## Features
|
|
19
|
+
Fill in here.
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
`codec.py` Can be included in a project and used as a local module or installed and used from `pip` :
|
|
23
|
+
```
|
|
24
|
+
pip install --pre syncoder
|
|
25
|
+
```
|
|
26
|
+
Note the requirement for the `--pre` flag as there is no official post release yet.
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
To be completed. For now see `tests/test_codec_pytest.py` for examples
|
|
30
|
+
|
|
31
|
+
## Testing
|
|
32
|
+
It is sufficient to run
|
|
33
|
+
```
|
|
34
|
+
pytest
|
|
35
|
+
```
|
|
36
|
+
from the root directory. Currently coverage is only for features and parameters in active use. More tests to come.
|
|
37
|
+
|
|
38
|
+
## License
|
|
39
|
+
BSD-3 clause. see `LICENSE` file in this directory.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# SyNCODER
|
|
2
|
+
|
|
3
|
+
## Project Overview
|
|
4
|
+
A DNA codec. (Fill in.)
|
|
5
|
+
|
|
6
|
+
## Features
|
|
7
|
+
Fill in here.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
`codec.py` Can be included in a project and used as a local module or installed and used from `pip` :
|
|
11
|
+
```
|
|
12
|
+
pip install --pre syncoder
|
|
13
|
+
```
|
|
14
|
+
Note the requirement for the `--pre` flag as there is no official post release yet.
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
To be completed. For now see `tests/test_codec_pytest.py` for examples
|
|
18
|
+
|
|
19
|
+
## Testing
|
|
20
|
+
It is sufficient to run
|
|
21
|
+
```
|
|
22
|
+
pytest
|
|
23
|
+
```
|
|
24
|
+
from the root directory. Currently coverage is only for features and parameters in active use. More tests to come.
|
|
25
|
+
|
|
26
|
+
## License
|
|
27
|
+
BSD-3 clause. see `LICENSE` file in this directory.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "syncoder"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.1a1"
|
|
4
4
|
description = "A codec for DNA data storage"
|
|
5
5
|
authors = [
|
|
6
6
|
{ name="Christopher Takahashi", email="cnt@uw.edu" }
|
|
@@ -19,4 +19,9 @@ packages = ["syncoder"]
|
|
|
19
19
|
|
|
20
20
|
[build-system]
|
|
21
21
|
requires = ["hatchling >= 1.26"]
|
|
22
|
-
build-backend = "hatchling.build"
|
|
22
|
+
build-backend = "hatchling.build"
|
|
23
|
+
|
|
24
|
+
[tool.pytest.ini_options]
|
|
25
|
+
testpaths = [
|
|
26
|
+
"tests"
|
|
27
|
+
]
|
|
@@ -2,7 +2,8 @@ import os
|
|
|
2
2
|
import struct
|
|
3
3
|
import itertools
|
|
4
4
|
import logging
|
|
5
|
-
|
|
5
|
+
import typing
|
|
6
|
+
from typing import Optional,Union, Callable, Tuple #requires python 3.5 or later
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
import numpy as np
|
|
@@ -18,7 +19,7 @@ import reedsolo
|
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
lipsum = b"Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum." #cspell:disable-line
|
|
21
|
-
|
|
22
|
+
|
|
22
23
|
|
|
23
24
|
def _int_to_baseN(n:int,base:int,length:int=-1)->list[int]:
|
|
24
25
|
"""Converts an integer to a base N number in little endian order.
|
|
@@ -255,7 +256,7 @@ class BaseNBlockCodec:
|
|
|
255
256
|
|
|
256
257
|
return ic_chunks
|
|
257
258
|
|
|
258
|
-
def decode(self, data: list[list[int]], index_start: int = 0) -> bytes:
|
|
259
|
+
def decode(self, data: list[list[int]], index_start: int = 0) -> Tuple[bytes,ArrayLike,ArrayLike,ArrayLike]:
|
|
259
260
|
"""
|
|
260
261
|
Decodes the given data.
|
|
261
262
|
|
|
@@ -274,18 +275,20 @@ class BaseNBlockCodec:
|
|
|
274
275
|
"""
|
|
275
276
|
# inner decode
|
|
276
277
|
## TODO: handle erasures in inner code.
|
|
277
|
-
|
|
278
|
+
|
|
279
|
+
# I'm sure it'll be a (FieldArray,int) ----------------V
|
|
280
|
+
chunked_data:list[Tuple[galois.FieldArray,int]] = [self.inner_coder.decode(d,errors=True) for d in data] #type:ignore
|
|
278
281
|
|
|
279
282
|
|
|
280
283
|
# handle outer decode
|
|
281
|
-
ordered_chunks = [None] * self.n_strands
|
|
282
|
-
chunk_errors = [-1] * self.n_strands
|
|
284
|
+
ordered_chunks:list[bytes|None] = [None] * self.n_strands
|
|
285
|
+
chunk_errors:list[int] = [-1] * self.n_strands
|
|
283
286
|
for chunk in chunked_data:
|
|
284
287
|
chunk_int = _baseN_to_int(chunk[0].tolist(), self.inner_coder.field.order)
|
|
285
288
|
chunk_index = chunk_int >> (self.data_chunk_size * 8) - index_start
|
|
286
289
|
_mask = (2 ** (self.data_chunk_size * 8)) - 1
|
|
287
290
|
chunk_int = chunk_int & _mask
|
|
288
|
-
chunk_bytes = chunk_int.to_bytes(self.data_chunk_size, 'little')
|
|
291
|
+
chunk_bytes:bytes = chunk_int.to_bytes(self.data_chunk_size, 'little')
|
|
289
292
|
#print(chunk_index)
|
|
290
293
|
try:
|
|
291
294
|
ordered_chunks[chunk_index] = chunk_bytes
|
|
@@ -299,6 +302,8 @@ class BaseNBlockCodec:
|
|
|
299
302
|
if c is None:
|
|
300
303
|
erasures.append(i) # remember where erasure is
|
|
301
304
|
ordered_chunks[i] = b'\x00' * self.data_chunk_size # fill in erasures with zeros
|
|
305
|
+
#we're all bytes now, but the checker can't tell that.
|
|
306
|
+
ordered_chunks_bytes:list[bytes] = typing.cast(list[bytes],ordered_chunks)
|
|
302
307
|
|
|
303
308
|
if self.outer_coder.c_exp==8:
|
|
304
309
|
outer_dtype = np.uint8
|
|
@@ -306,7 +311,7 @@ class BaseNBlockCodec:
|
|
|
306
311
|
outer_dtype = np.uint16
|
|
307
312
|
else:
|
|
308
313
|
raise ValueError("Unsupported outer code byte size.")
|
|
309
|
-
data_np_encoded = np.array([np.frombuffer(c, dtype=outer_dtype) for c in
|
|
314
|
+
data_np_encoded = np.array([np.frombuffer(c, dtype=outer_dtype) for c in ordered_chunks_bytes]).transpose()
|
|
310
315
|
data_decoded_results = [self.outer_coder.decode(dc, erase_pos=erasures) for dc in data_np_encoded]
|
|
311
316
|
data_decoded_chunks = np.array([d[0] for d in data_decoded_results],dtype=outer_dtype).transpose()
|
|
312
317
|
data_decoded = data_decoded_chunks.flatten().tobytes()
|
|
@@ -336,7 +341,7 @@ class BaseNBlockCodec:
|
|
|
336
341
|
_default_b32_alphabet = ['GCT', 'ACT', 'AGT', 'CTC', 'TGC', 'GAG', 'GCA', 'ATG', 'AGA', 'AGC', 'CAC', 'CAT', 'CAG', 'CTG', 'TCT', 'GAC', 'GTC', 'GTG', 'ACA', 'ACG', 'ATC', 'CTA', 'CGA', 'CGT', 'TAC', 'TAG', 'TCA', 'TCG', 'TGA', 'TGT', 'GAT', 'GTA']
|
|
337
342
|
_default_b32_alphabet_alt = ['ATA', 'AAC', 'CCT', 'TAT', 'CCG', 'TGG', 'CGC', 'CGG', 'GTT', 'GGT', 'GCG', 'TCC', 'CCA', 'GGA', 'ACC', 'AGG', 'TAA', 'GCC', 'AAA', 'CCC', 'GGG', 'GGC', 'TTA', 'TTT', 'GAA', 'AAG', 'CTT', 'TTG', 'TTC', 'AAT', 'CAA', 'ATT']
|
|
338
343
|
|
|
339
|
-
def find_avoid(seq:str, avoids:list)->tuple[int]:
|
|
344
|
+
def find_avoid(seq:str, avoids:list)->tuple[int,int]:
|
|
340
345
|
"""find index and length of avoided sequence """
|
|
341
346
|
for a in avoids:
|
|
342
347
|
loc = seq.find(a)
|
|
@@ -374,39 +379,53 @@ def longest_binder(a:str,b:str):
|
|
|
374
379
|
|
|
375
380
|
|
|
376
381
|
|
|
377
|
-
def b32_to_DNA_optimize(file_data:list[
|
|
382
|
+
def b32_to_DNA_optimize(file_data:list[ArrayLike],words:list[str], alternate_words:list[str], mask:Union[ArrayLike,None]=None, nmasked=-1, penalty_fn:Union[Callable[[str],int],None]=None)->list[Tuple[bytes,int]]:
|
|
378
383
|
"""
|
|
379
384
|
TODO: rename to bN_to_DNA_optimize, and add test.
|
|
385
|
+
file_data: 2d list of integral types (symbols)
|
|
380
386
|
mask: list of ints. 0 for word, 1 for alternate word, -1 for don't care. None for no mask.
|
|
387
|
+
nmasked: number of masked words. -1 for all.
|
|
381
388
|
penalty_fn: function that takes a DNA sequence and returns a score. penalty_fn(DNA_seq:str)->int
|
|
382
389
|
"""
|
|
383
|
-
|
|
390
|
+
if nmasked<0: #mask all
|
|
391
|
+
return [b32_to_DNA_optimize_single(x,words,alternate_words,mask,penalty_fn=penalty_fn) for x in file_data]
|
|
392
|
+
else:
|
|
393
|
+
masked_part = [b32_to_DNA_optimize_single(x,words,alternate_words,mask,penalty_fn=penalty_fn) for x in file_data[0:nmasked]]
|
|
394
|
+
unmasked_part = [b32_to_DNA_optimize_single(x,words,alternate_words,mask=None,penalty_fn=penalty_fn) for x in file_data[nmasked:]]
|
|
395
|
+
return masked_part + unmasked_part
|
|
384
396
|
|
|
385
397
|
|
|
386
|
-
def b32_to_DNA_optimize_single(strand_data:
|
|
398
|
+
def b32_to_DNA_optimize_single(strand_data:ArrayLike, words:list[str], alternate_words:list[str], mask:Union[ArrayLike,None]=None ,penalty_fn=None)->Tuple[bytes,int]:
|
|
387
399
|
"""
|
|
388
400
|
TODO: rename to bN_to_DNA_optimize_single
|
|
389
401
|
TODO: add mask support.
|
|
402
|
+
strand_data: 1d list of integral types
|
|
390
403
|
mask: list of ints. 0 for word, 1 for alternate word, -1 for don't care. None for no mask.
|
|
391
404
|
"""
|
|
405
|
+
strand_data = np.array(strand_data)
|
|
406
|
+
|
|
392
407
|
if mask is not None:
|
|
393
|
-
|
|
408
|
+
mask = np.array(mask)
|
|
409
|
+
_m = np.full(len(strand_data),-1,dtype=np.int8)
|
|
410
|
+
_m[:len(mask)] = np.array(mask.copy()) #Probably don't need this copy.
|
|
411
|
+
mask = _m
|
|
412
|
+
mutable_ind = np.where(mask==-1)
|
|
413
|
+
mask[mutable_ind] = 0
|
|
394
414
|
#raise NotImplementedError("mask not implemented yet")
|
|
395
415
|
else:
|
|
416
|
+
mask = np.zeros(len(strand_data),dtype=np.uint8)
|
|
396
417
|
mutable_ind = np.arange(len(strand_data))
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
strand_data = np.array(strand_data)
|
|
401
|
-
words = np.array(list(zip(words,alternate_words)),dtype="S")
|
|
418
|
+
|
|
419
|
+
words_lookup = np.array(list(zip(words,alternate_words)),dtype="S")
|
|
402
420
|
picks = np.zeros(len(strand_data),dtype=np.uint8)
|
|
403
421
|
picks[0:len(mask)] = mask
|
|
404
|
-
dna_seq = b"".join(
|
|
422
|
+
dna_seq = b"".join(words_lookup[strand_data,picks])
|
|
405
423
|
if penalty_fn is None:
|
|
406
424
|
return dna_seq,0
|
|
407
425
|
score = penalty_fn(dna_seq)
|
|
408
426
|
|
|
409
427
|
#greedy search, stop when you can't find a better solution one step away.
|
|
428
|
+
#this may end on a local minimum.
|
|
410
429
|
icount = 0
|
|
411
430
|
while True:
|
|
412
431
|
iteration_best_picks = picks.copy()
|
|
@@ -415,7 +434,7 @@ def b32_to_DNA_optimize_single(strand_data:list[int],words:list[str], alternate_
|
|
|
415
434
|
for i in mutable_ind:
|
|
416
435
|
new_picks = picks.copy()
|
|
417
436
|
new_picks[i] = not new_picks[i]
|
|
418
|
-
new_dna_seq = b"".join(
|
|
437
|
+
new_dna_seq = b"".join(words_lookup[strand_data,new_picks])
|
|
419
438
|
new_score = penalty_fn(new_dna_seq)
|
|
420
439
|
if new_score < iteration_best_score:
|
|
421
440
|
iteration_best_picks = new_picks
|
|
@@ -457,11 +476,11 @@ def b32_to_DNA(file_data:list[list[int]],words:list[str], alternate_words:list[s
|
|
|
457
476
|
dna.append(_s)
|
|
458
477
|
return dna
|
|
459
478
|
|
|
460
|
-
def dna_to_bN(dna: Union[list[str],list[bytes]] ,words:list[str], alternate_words:list[str])->list[int]:
|
|
461
|
-
""" takes a list of strings and converts them to a list of base N ints"""
|
|
479
|
+
def dna_to_bN(dna: Union[list[str],list[bytes]] ,words:list[str], alternate_words:list[str])->list[list[int]]:
|
|
480
|
+
""" takes a list of strings and converts them to a list of lists of base N ints"""
|
|
462
481
|
|
|
463
|
-
if isinstance(dna[0],bytes):
|
|
464
|
-
dna = [x.decode() for x in dna]
|
|
482
|
+
if isinstance(dna[0],bytes): #assume the rest are bytes too.
|
|
483
|
+
dna = [x.decode() for x in typing.cast(list[bytes],dna)]
|
|
465
484
|
|
|
466
485
|
wordlen = len(words[0])
|
|
467
486
|
#build the reverse lookup table
|
|
@@ -471,16 +490,16 @@ def dna_to_bN(dna: Union[list[str],list[bytes]] ,words:list[str], alternate_word
|
|
|
471
490
|
bNlut[alternate_words[ind]] = ind
|
|
472
491
|
|
|
473
492
|
|
|
474
|
-
bNdatalist = []
|
|
493
|
+
bNdatalist:list[list[int]] = []
|
|
475
494
|
for d in dna:
|
|
476
|
-
bNdata = []
|
|
495
|
+
bNdata:list[int] = []
|
|
477
496
|
for ind in range(0,len(d),wordlen):
|
|
478
497
|
w = d[ind:ind+wordlen]
|
|
479
498
|
bNdata.append(bNlut[w])
|
|
480
499
|
bNdatalist.append(bNdata)
|
|
481
500
|
return bNdatalist
|
|
482
501
|
|
|
483
|
-
def dna_to_b32(dna: Union[list[str],list[bytes]] ,words:list[str], alternate_words:list[str])->list[int]:
|
|
502
|
+
def dna_to_b32(dna: Union[list[str],list[bytes]] ,words:list[str], alternate_words:list[str])->list[list[int]]:
|
|
484
503
|
assert len(words) == 32
|
|
485
504
|
return dna_to_bN(dna,words,alternate_words)
|
|
486
505
|
|
|
@@ -602,15 +621,21 @@ def remove_bytes(data:bytes, insert:int|bytes, chunk_len:int, num_inserts:int) -
|
|
|
602
621
|
Returns:
|
|
603
622
|
bytes: The modified byte array with the inserted bytes removed.
|
|
604
623
|
"""
|
|
624
|
+
have_insert_seq = False
|
|
605
625
|
if isinstance(insert,bytes):
|
|
606
626
|
insert_len = len(insert)
|
|
627
|
+
have_insert_seq = True
|
|
628
|
+
elif isinstance(insert,int):
|
|
629
|
+
if insert <= 0:
|
|
630
|
+
raise ValueError("insert must be a positive integer")
|
|
631
|
+
insert_len:int = insert
|
|
607
632
|
else:
|
|
608
|
-
insert
|
|
633
|
+
raise ValueError("insert must be a bytes or int")
|
|
609
634
|
|
|
610
635
|
data = bytearray(data)
|
|
611
636
|
for i in reversed(range(num_inserts)):
|
|
612
637
|
ins_index = i * chunk_len
|
|
613
|
-
if
|
|
638
|
+
if have_insert_seq:
|
|
614
639
|
if data[ins_index:ins_index + insert_len] != insert:
|
|
615
640
|
raise ValueError("insert bytes do not match bytes to be removed")
|
|
616
641
|
del data[ins_index:ins_index + insert_len]
|
|
@@ -4,7 +4,7 @@ import random
|
|
|
4
4
|
import numpy as np
|
|
5
5
|
from itertools import product
|
|
6
6
|
|
|
7
|
-
import codec
|
|
7
|
+
import syncoder as codec
|
|
8
8
|
|
|
9
9
|
#logging.basicConfig(level=logging.DEBUG,stream=sys.stdout)
|
|
10
10
|
#numba_logger = logging.getLogger('numba')
|
|
@@ -27,7 +27,7 @@ def test_encode_decode():
|
|
|
27
27
|
def test_coded_to_bases():
|
|
28
28
|
#TODO: test optimize too
|
|
29
29
|
|
|
30
|
-
test_encoded_data = np.random.randint(0,32,(10,31))
|
|
30
|
+
test_encoded_data = np.random.randint(0,32,(10,31),dtype=np.uint8)
|
|
31
31
|
DNA_with_scores = codec.b32_to_DNA_optimize(test_encoded_data,codec._default_b32_alphabet,codec._default_b32_alphabet_alt)
|
|
32
32
|
DNA = [x[0] for x in DNA_with_scores]
|
|
33
33
|
test_decoded_data = codec.dna_to_b32(DNA,codec._default_b32_alphabet,codec._default_b32_alphabet_alt)
|
|
@@ -54,6 +54,8 @@ def test_dna_to_bN():
|
|
|
54
54
|
assert np.array_equal(data, decode_data)
|
|
55
55
|
|
|
56
56
|
def test_dna_to_bytes():
|
|
57
|
+
_int_to_baseN = codec.codec._int_to_baseN
|
|
58
|
+
|
|
57
59
|
#TODO: adapt to workwith not b32 too.
|
|
58
60
|
#answer = [64, 79, 231, 126, 228, 124, 3]
|
|
59
61
|
alphabet =codec._default_b32_alphabet
|
|
@@ -70,7 +72,7 @@ def test_dna_to_bytes():
|
|
|
70
72
|
|
|
71
73
|
dna_bytes, mask = codec.dna_to_bytes(DNA,alphabet,alphabet_alt)
|
|
72
74
|
|
|
73
|
-
dna_baseN =
|
|
75
|
+
dna_baseN = _int_to_baseN(int.from_bytes(dna_bytes,"little"),len(alphabet))
|
|
74
76
|
assert dna_baseN == answer32
|
|
75
77
|
|
|
76
78
|
_t = np.array(dna_baseN)
|
|
@@ -78,3 +80,28 @@ def test_dna_to_bytes():
|
|
|
78
80
|
generated_dna = "".join(full_alphabet[dna_baseN_with_alt])
|
|
79
81
|
|
|
80
82
|
assert generated_dna == DNA
|
|
83
|
+
|
|
84
|
+
def test_inserts():
|
|
85
|
+
coder = codec.BaseNBlockCodec(inner_alphabet_size=32,inner_d=5,inner_n=29,n_strands=2000,n_redundant_strands=100)
|
|
86
|
+
n_inserts = 200
|
|
87
|
+
insert = "GCTCTTCCCACCACCATTGCCTTCTTCTTG"
|
|
88
|
+
insert_bytes, imask = codec.dna_to_bytes(insert,codec._default_b32_alphabet,codec._default_b32_alphabet_alt)
|
|
89
|
+
total_ibytes = len(insert_bytes)*n_inserts
|
|
90
|
+
data_size = coder.block_capacity_bytes - total_ibytes
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
test_data = np.random.randint(0,255,data_size,dtype=np.uint8).tobytes()
|
|
94
|
+
|
|
95
|
+
inserted_data = codec.insert_bytes(test_data,insert_bytes,coder.data_chunk_size,n_inserts)
|
|
96
|
+
assert len(inserted_data) == coder.block_capacity_bytes
|
|
97
|
+
|
|
98
|
+
enc_d = coder.encode(inserted_data)
|
|
99
|
+
data_decodec,__oe,__oer,__ie = coder.decode(enc_d)
|
|
100
|
+
assert data_decodec == inserted_data
|
|
101
|
+
|
|
102
|
+
test_dna_out = codec.b32_to_DNA_optimize(enc_d,codec._default_b32_alphabet,codec._default_b32_alphabet_alt,mask=imask,nmasked=n_inserts)
|
|
103
|
+
test_dna = [x[0] for x in test_dna_out]
|
|
104
|
+
for strand in test_dna[:n_inserts]:
|
|
105
|
+
assert strand.decode().startswith(insert)
|
|
106
|
+
|
|
107
|
+
assert codec.dna_to_bN(test_dna,codec._default_b32_alphabet,codec._default_b32_alphabet_alt) == enc_d
|
syncoder-0.0.0/.gitignore
DELETED
syncoder-0.0.0/PKG-INFO
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: syncoder
|
|
3
|
-
Version: 0.0.0
|
|
4
|
-
Summary: A codec for DNA data storage
|
|
5
|
-
Project-URL: Homepage, https://example.com/TBD
|
|
6
|
-
Project-URL: Issues, https://example.com/TBD/issues
|
|
7
|
-
Author-email: Christopher Takahashi <cnt@uw.edu>
|
|
8
|
-
License-Expression: BSD-3-Clause
|
|
9
|
-
License-File: LICENSE
|
|
10
|
-
Requires-Python: >=3.9
|
|
11
|
-
Description-Content-Type: text/markdown
|
|
12
|
-
|
|
13
|
-
Readme coming soon.
|
syncoder-0.0.0/README.md
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
Readme coming soon.
|
syncoder-0.0.0/foo.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__all__ = ["bar.nextfn"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|