syncoder 0.0.0__tar.gz → 0.0.2a1__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.2a1/.gitignore +18 -0
- syncoder-0.0.2a1/PKG-INFO +42 -0
- syncoder-0.0.2a1/README.md +27 -0
- {syncoder-0.0.0 → syncoder-0.0.2a1/environments}/conda_environment.yml +1 -1
- {syncoder-0.0.0 → syncoder-0.0.2a1/environments}/conda_environment_dev.yml +3 -2
- {syncoder-0.0.0 → syncoder-0.0.2a1/environments}/conda_environment_test.yml +1 -1
- {syncoder-0.0.0 → syncoder-0.0.2a1}/pyproject.toml +15 -2
- syncoder-0.0.2a1/src/syncoder/__init__.py +6 -0
- {syncoder-0.0.0 → syncoder-0.0.2a1}/src/syncoder/codec.py +57 -32
- {syncoder-0.0.0 → syncoder-0.0.2a1}/tests/test_codec_pytest.py +33 -4
- 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.2a1}/.vscode/.gitignore +0 -0
- {syncoder-0.0.0 → syncoder-0.0.2a1}/LICENSE +0 -0
|
@@ -0,0 +1,18 @@
|
|
|
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
|
+
.pypirc
|
|
17
|
+
#any actual py files are in src. anything in root is supposed to just for development.
|
|
18
|
+
/*.py
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: syncoder
|
|
3
|
+
Version: 0.0.2a1
|
|
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
|
+
Requires-Dist: galois>=0.4
|
|
12
|
+
Requires-Dist: numpy>=1.20
|
|
13
|
+
Requires-Dist: reedsolo>=1.7
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# SyNCODER
|
|
17
|
+
|
|
18
|
+
## Project Overview
|
|
19
|
+
A DNA codec. (Fill in.)
|
|
20
|
+
|
|
21
|
+
## Features
|
|
22
|
+
Fill in here.
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
`codec.py` Can be included in a project and used as a local module or installed and used from `pip` :
|
|
26
|
+
```
|
|
27
|
+
pip install --pre syncoder
|
|
28
|
+
```
|
|
29
|
+
Note the requirement for the `--pre` flag as there is no official post release yet.
|
|
30
|
+
|
|
31
|
+
## Usage
|
|
32
|
+
To be completed. For now see `tests/test_codec_pytest.py` for examples
|
|
33
|
+
|
|
34
|
+
## Testing
|
|
35
|
+
It is sufficient to run
|
|
36
|
+
```
|
|
37
|
+
pytest
|
|
38
|
+
```
|
|
39
|
+
from the root directory. Currently coverage is only for features and parameters in active use. More tests to come.
|
|
40
|
+
|
|
41
|
+
## License
|
|
42
|
+
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.
|
|
@@ -2,7 +2,7 @@ name: codec-dev
|
|
|
2
2
|
channels:
|
|
3
3
|
- conda-forge
|
|
4
4
|
dependencies:
|
|
5
|
-
- python
|
|
5
|
+
- python>=3.12
|
|
6
6
|
- numpy=2.*
|
|
7
7
|
- matplotlib=3.*
|
|
8
8
|
- pytest
|
|
@@ -13,7 +13,8 @@ dependencies:
|
|
|
13
13
|
- biopython
|
|
14
14
|
- jupyter
|
|
15
15
|
- ipykernel
|
|
16
|
-
- build
|
|
17
16
|
- pip:
|
|
18
17
|
- galois==0.4.*
|
|
18
|
+
- build
|
|
19
|
+
- twine
|
|
19
20
|
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "syncoder"
|
|
3
|
-
|
|
3
|
+
dynamic = ["version"]
|
|
4
|
+
dependencies = [
|
|
5
|
+
"numpy>=1.20",
|
|
6
|
+
"galois>=0.4",
|
|
7
|
+
"reedsolo>=1.7"
|
|
8
|
+
]
|
|
4
9
|
description = "A codec for DNA data storage"
|
|
5
10
|
authors = [
|
|
6
11
|
{ name="Christopher Takahashi", email="cnt@uw.edu" }
|
|
@@ -19,4 +24,12 @@ packages = ["syncoder"]
|
|
|
19
24
|
|
|
20
25
|
[build-system]
|
|
21
26
|
requires = ["hatchling >= 1.26"]
|
|
22
|
-
build-backend = "hatchling.build"
|
|
27
|
+
build-backend = "hatchling.build"
|
|
28
|
+
|
|
29
|
+
[tool.pytest.ini_options]
|
|
30
|
+
testpaths = [
|
|
31
|
+
"tests"
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[tool.hatch.version]
|
|
35
|
+
path = "src/syncoder/__init__.py"
|
|
@@ -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.
|
|
@@ -141,6 +142,7 @@ class BaseNBlockCodec:
|
|
|
141
142
|
# ^ = self.index_bits <-- in bits not bytes.
|
|
142
143
|
# note: actual k remains alphabet_size-1, but the code is shorted (0 padded) to inner_n symbols.
|
|
143
144
|
k = inner_n - (inner_d - 1)
|
|
145
|
+
self.inner_k = k
|
|
144
146
|
q = self.inner_coder.field.order
|
|
145
147
|
|
|
146
148
|
#number of bytes needed to store the index
|
|
@@ -247,7 +249,7 @@ class BaseNBlockCodec:
|
|
|
247
249
|
chunk += index<<(self.data_chunk_size*8)
|
|
248
250
|
index += 1
|
|
249
251
|
#but.. we still need to pad out the symbol stream.
|
|
250
|
-
chunk = _int_to_baseN(chunk,self.inner_coder.field.order,length=self.
|
|
252
|
+
chunk = _int_to_baseN(chunk,self.inner_coder.field.order,length=self.inner_k)
|
|
251
253
|
chunks.append(chunk)
|
|
252
254
|
|
|
253
255
|
#apply inner code
|
|
@@ -255,7 +257,7 @@ class BaseNBlockCodec:
|
|
|
255
257
|
|
|
256
258
|
return ic_chunks
|
|
257
259
|
|
|
258
|
-
def decode(self, data: list[list[int]], index_start: int = 0) -> bytes:
|
|
260
|
+
def decode(self, data: list[list[int]], index_start: int = 0) -> Tuple[bytes,ArrayLike,ArrayLike,ArrayLike]:
|
|
259
261
|
"""
|
|
260
262
|
Decodes the given data.
|
|
261
263
|
|
|
@@ -274,18 +276,20 @@ class BaseNBlockCodec:
|
|
|
274
276
|
"""
|
|
275
277
|
# inner decode
|
|
276
278
|
## TODO: handle erasures in inner code.
|
|
277
|
-
|
|
279
|
+
|
|
280
|
+
# I'm sure it'll be a (FieldArray,int) ----------------V
|
|
281
|
+
chunked_data:list[Tuple[galois.FieldArray,int]] = [self.inner_coder.decode(d,errors=True) for d in data] #type:ignore
|
|
278
282
|
|
|
279
283
|
|
|
280
284
|
# handle outer decode
|
|
281
|
-
ordered_chunks = [None] * self.n_strands
|
|
282
|
-
chunk_errors = [-1] * self.n_strands
|
|
285
|
+
ordered_chunks:list[bytes|None] = [None] * self.n_strands
|
|
286
|
+
chunk_errors:list[int] = [-1] * self.n_strands
|
|
283
287
|
for chunk in chunked_data:
|
|
284
288
|
chunk_int = _baseN_to_int(chunk[0].tolist(), self.inner_coder.field.order)
|
|
285
289
|
chunk_index = chunk_int >> (self.data_chunk_size * 8) - index_start
|
|
286
290
|
_mask = (2 ** (self.data_chunk_size * 8)) - 1
|
|
287
291
|
chunk_int = chunk_int & _mask
|
|
288
|
-
chunk_bytes = chunk_int.to_bytes(self.data_chunk_size, 'little')
|
|
292
|
+
chunk_bytes:bytes = chunk_int.to_bytes(self.data_chunk_size, 'little')
|
|
289
293
|
#print(chunk_index)
|
|
290
294
|
try:
|
|
291
295
|
ordered_chunks[chunk_index] = chunk_bytes
|
|
@@ -299,6 +303,8 @@ class BaseNBlockCodec:
|
|
|
299
303
|
if c is None:
|
|
300
304
|
erasures.append(i) # remember where erasure is
|
|
301
305
|
ordered_chunks[i] = b'\x00' * self.data_chunk_size # fill in erasures with zeros
|
|
306
|
+
#we're all bytes now, but the checker can't tell that.
|
|
307
|
+
ordered_chunks_bytes:list[bytes] = typing.cast(list[bytes],ordered_chunks)
|
|
302
308
|
|
|
303
309
|
if self.outer_coder.c_exp==8:
|
|
304
310
|
outer_dtype = np.uint8
|
|
@@ -306,7 +312,7 @@ class BaseNBlockCodec:
|
|
|
306
312
|
outer_dtype = np.uint16
|
|
307
313
|
else:
|
|
308
314
|
raise ValueError("Unsupported outer code byte size.")
|
|
309
|
-
data_np_encoded = np.array([np.frombuffer(c, dtype=outer_dtype) for c in
|
|
315
|
+
data_np_encoded = np.array([np.frombuffer(c, dtype=outer_dtype) for c in ordered_chunks_bytes]).transpose()
|
|
310
316
|
data_decoded_results = [self.outer_coder.decode(dc, erase_pos=erasures) for dc in data_np_encoded]
|
|
311
317
|
data_decoded_chunks = np.array([d[0] for d in data_decoded_results],dtype=outer_dtype).transpose()
|
|
312
318
|
data_decoded = data_decoded_chunks.flatten().tobytes()
|
|
@@ -336,7 +342,7 @@ class BaseNBlockCodec:
|
|
|
336
342
|
_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
343
|
_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
344
|
|
|
339
|
-
def find_avoid(seq:str, avoids:list)->tuple[int]:
|
|
345
|
+
def find_avoid(seq:str, avoids:list)->tuple[int,int]:
|
|
340
346
|
"""find index and length of avoided sequence """
|
|
341
347
|
for a in avoids:
|
|
342
348
|
loc = seq.find(a)
|
|
@@ -374,39 +380,52 @@ def longest_binder(a:str,b:str):
|
|
|
374
380
|
|
|
375
381
|
|
|
376
382
|
|
|
377
|
-
def b32_to_DNA_optimize(file_data:list[
|
|
383
|
+
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
384
|
"""
|
|
379
385
|
TODO: rename to bN_to_DNA_optimize, and add test.
|
|
386
|
+
file_data: 2d list of integral types (symbols)
|
|
380
387
|
mask: list of ints. 0 for word, 1 for alternate word, -1 for don't care. None for no mask.
|
|
388
|
+
nmasked: number of masked words. -1 for all.
|
|
381
389
|
penalty_fn: function that takes a DNA sequence and returns a score. penalty_fn(DNA_seq:str)->int
|
|
382
|
-
"""
|
|
383
|
-
|
|
390
|
+
"""
|
|
391
|
+
if nmasked<0: #mask all
|
|
392
|
+
return [b32_to_DNA_optimize_single(x,words,alternate_words,mask,penalty_fn=penalty_fn) for x in file_data]
|
|
393
|
+
else:
|
|
394
|
+
masked_part = [b32_to_DNA_optimize_single(x,words,alternate_words,mask,penalty_fn=penalty_fn) for x in file_data[0:nmasked]]
|
|
395
|
+
unmasked_part = [b32_to_DNA_optimize_single(x,words,alternate_words,mask=None,penalty_fn=penalty_fn) for x in file_data[nmasked:]]
|
|
396
|
+
return masked_part + unmasked_part
|
|
384
397
|
|
|
385
398
|
|
|
386
|
-
def b32_to_DNA_optimize_single(strand_data:
|
|
399
|
+
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
400
|
"""
|
|
388
401
|
TODO: rename to bN_to_DNA_optimize_single
|
|
389
|
-
|
|
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')
|
|
@@ -23,12 +23,14 @@ def test_encode_decode():
|
|
|
23
23
|
|
|
24
24
|
out_text = c.decode( coded )[0]
|
|
25
25
|
assert in_text == out_text
|
|
26
|
+
#check inner_n is causing the correct number of bases
|
|
27
|
+
assert len(coded[0]) == 30
|
|
26
28
|
|
|
27
29
|
def test_coded_to_bases():
|
|
28
30
|
#TODO: test optimize too
|
|
29
31
|
|
|
30
|
-
test_encoded_data = np.random.randint(0,32,(10,31))
|
|
31
|
-
DNA_with_scores = codec.b32_to_DNA_optimize(test_encoded_data,codec._default_b32_alphabet,codec._default_b32_alphabet_alt)
|
|
32
|
+
test_encoded_data = np.random.randint(0,32,(10,31),dtype=np.uint8)
|
|
33
|
+
DNA_with_scores = codec.b32_to_DNA_optimize(test_encoded_data,codec._default_b32_alphabet,codec._default_b32_alphabet_alt) # type: ignore
|
|
32
34
|
DNA = [x[0] for x in DNA_with_scores]
|
|
33
35
|
test_decoded_data = codec.dna_to_b32(DNA,codec._default_b32_alphabet,codec._default_b32_alphabet_alt)
|
|
34
36
|
print(test_encoded_data.flatten())
|
|
@@ -54,6 +56,8 @@ def test_dna_to_bN():
|
|
|
54
56
|
assert np.array_equal(data, decode_data)
|
|
55
57
|
|
|
56
58
|
def test_dna_to_bytes():
|
|
59
|
+
_int_to_baseN = codec.codec._int_to_baseN
|
|
60
|
+
|
|
57
61
|
#TODO: adapt to workwith not b32 too.
|
|
58
62
|
#answer = [64, 79, 231, 126, 228, 124, 3]
|
|
59
63
|
alphabet =codec._default_b32_alphabet
|
|
@@ -70,7 +74,7 @@ def test_dna_to_bytes():
|
|
|
70
74
|
|
|
71
75
|
dna_bytes, mask = codec.dna_to_bytes(DNA,alphabet,alphabet_alt)
|
|
72
76
|
|
|
73
|
-
dna_baseN =
|
|
77
|
+
dna_baseN = _int_to_baseN(int.from_bytes(dna_bytes,"little"),len(alphabet))
|
|
74
78
|
assert dna_baseN == answer32
|
|
75
79
|
|
|
76
80
|
_t = np.array(dna_baseN)
|
|
@@ -78,3 +82,28 @@ def test_dna_to_bytes():
|
|
|
78
82
|
generated_dna = "".join(full_alphabet[dna_baseN_with_alt])
|
|
79
83
|
|
|
80
84
|
assert generated_dna == DNA
|
|
85
|
+
|
|
86
|
+
def test_inserts():
|
|
87
|
+
coder = codec.BaseNBlockCodec(inner_alphabet_size=32,inner_d=5,inner_n=29,n_strands=2000,n_redundant_strands=100)
|
|
88
|
+
n_inserts = 200
|
|
89
|
+
insert = "GCTCTTCCCACCACCATTGCCTTCTTCTTG"
|
|
90
|
+
insert_bytes, imask = codec.dna_to_bytes(insert,codec._default_b32_alphabet,codec._default_b32_alphabet_alt)
|
|
91
|
+
total_ibytes = len(insert_bytes)*n_inserts
|
|
92
|
+
data_size = coder.block_capacity_bytes - total_ibytes
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
test_data = np.random.randint(0,255,data_size,dtype=np.uint8).tobytes()
|
|
96
|
+
|
|
97
|
+
inserted_data = codec.insert_bytes(test_data,insert_bytes,coder.data_chunk_size,n_inserts)
|
|
98
|
+
assert len(inserted_data) == coder.block_capacity_bytes
|
|
99
|
+
|
|
100
|
+
enc_d = coder.encode(inserted_data)
|
|
101
|
+
data_decodec,__oe,__oer,__ie = coder.decode(enc_d)
|
|
102
|
+
assert data_decodec == inserted_data
|
|
103
|
+
|
|
104
|
+
test_dna_out = codec.b32_to_DNA_optimize(enc_d,codec._default_b32_alphabet,codec._default_b32_alphabet_alt,mask=imask,nmasked=n_inserts) # type: ignore
|
|
105
|
+
test_dna = [x[0] for x in test_dna_out]
|
|
106
|
+
for strand in test_dna[:n_inserts]:
|
|
107
|
+
assert strand.decode().startswith(insert)
|
|
108
|
+
|
|
109
|
+
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
|