syncoder 0.0.5a0__tar.gz → 0.0.6a1__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.5a0 → syncoder-0.0.6a1}/PKG-INFO +7 -6
- {syncoder-0.0.5a0 → syncoder-0.0.6a1}/README.md +29 -27
- {syncoder-0.0.5a0 → syncoder-0.0.6a1}/environments/conda_environment.yml +3 -3
- {syncoder-0.0.5a0 → syncoder-0.0.6a1}/environments/conda_environment_dev.yml +3 -3
- syncoder-0.0.6a1/environments/conda_environment_pipeline.yml +14 -0
- {syncoder-0.0.5a0 → syncoder-0.0.6a1}/environments/conda_environment_test.yml +3 -3
- {syncoder-0.0.5a0 → syncoder-0.0.6a1}/pyproject.toml +33 -34
- {syncoder-0.0.5a0 → syncoder-0.0.6a1}/src/syncoder/__init__.py +6 -6
- {syncoder-0.0.5a0 → syncoder-0.0.6a1}/src/syncoder/codec.py +272 -59
- syncoder-0.0.6a1/src/syncoder/sector01.py +57 -0
- {syncoder-0.0.5a0 → syncoder-0.0.6a1}/tests/test_codec_pytest.py +26 -2
- {syncoder-0.0.5a0 → syncoder-0.0.6a1}/.gitignore +0 -0
- {syncoder-0.0.5a0 → syncoder-0.0.6a1}/.vscode/.gitignore +0 -0
- {syncoder-0.0.5a0 → syncoder-0.0.6a1}/LICENSE +0 -0
- {syncoder-0.0.5a0 → syncoder-0.0.6a1}/src/syncoder/reedsoloMulti.py +0 -0
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: syncoder
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.6a1
|
|
4
4
|
Summary: A codec for DNA data storage
|
|
5
5
|
Project-URL: Homepage, https://example.com/TBD
|
|
6
6
|
Project-URL: Issues, https://example.com/TBD/issues
|
|
7
7
|
Author-email: Christopher Takahashi <cnt@uw.edu>
|
|
8
8
|
License-Expression: BSD-3-Clause
|
|
9
9
|
License-File: LICENSE
|
|
10
|
-
Requires-Python: >=3.
|
|
11
|
-
Requires-Dist: galois>=0.4
|
|
12
|
-
Requires-Dist: numpy>=
|
|
13
|
-
Requires-Dist: reedsolo>=1.7
|
|
10
|
+
Requires-Python: >=3.11
|
|
11
|
+
Requires-Dist: galois>=0.4.8
|
|
12
|
+
Requires-Dist: numpy>=2
|
|
14
13
|
Description-Content-Type: text/markdown
|
|
15
14
|
|
|
16
15
|
# SyNCODER
|
|
@@ -20,6 +19,8 @@ A DNA codec. (Fill in.)
|
|
|
20
19
|
|
|
21
20
|
## Features
|
|
22
21
|
Fill in here.
|
|
22
|
+
### DDSA Sector 0/1 generator
|
|
23
|
+
(Status: incomplete) Generates [sector 0](https://www.snia.org/standards/technology-standards-software/standards-portfolio/dna-data-storage-sector-zero) and [sector 1](https://www.snia.org/standards/technology-standards-software/standards-portfolio/dna-data-storage-sector-one) compliant with the DNA Data Storange Alliance (DDSA) [DNA Archive Rosetta Stone specifiation](https://dnastoragealliance.org/publications/). Note: The maximum sector 1 size is currenty 325950 bytes (limited by reserved index size).
|
|
23
24
|
|
|
24
25
|
## Installation
|
|
25
26
|
`codec.py` Can be included in a project and used as a local module or installed and used from `pip` :
|
|
@@ -1,27 +1,29 @@
|
|
|
1
|
-
# SyNCODER
|
|
2
|
-
|
|
3
|
-
## Project Overview
|
|
4
|
-
A DNA codec. (Fill in.)
|
|
5
|
-
|
|
6
|
-
## Features
|
|
7
|
-
Fill in here.
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
1
|
+
# SyNCODER
|
|
2
|
+
|
|
3
|
+
## Project Overview
|
|
4
|
+
A DNA codec. (Fill in.)
|
|
5
|
+
|
|
6
|
+
## Features
|
|
7
|
+
Fill in here.
|
|
8
|
+
### DDSA Sector 0/1 generator
|
|
9
|
+
(Status: incomplete) Generates [sector 0](https://www.snia.org/standards/technology-standards-software/standards-portfolio/dna-data-storage-sector-zero) and [sector 1](https://www.snia.org/standards/technology-standards-software/standards-portfolio/dna-data-storage-sector-one) compliant with the DNA Data Storange Alliance (DDSA) [DNA Archive Rosetta Stone specifiation](https://dnastoragealliance.org/publications/). Note: The maximum sector 1 size is currenty 325950 bytes (limited by reserved index size).
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
`codec.py` Can be included in a project and used as a local module or installed and used from `pip` :
|
|
13
|
+
```
|
|
14
|
+
pip install --pre syncoder
|
|
15
|
+
```
|
|
16
|
+
Note the requirement for the `--pre` flag as there is no official post release yet.
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
To be completed. For now see `tests/test_codec_pytest.py` for examples
|
|
20
|
+
|
|
21
|
+
## Testing
|
|
22
|
+
It is sufficient to run
|
|
23
|
+
```
|
|
24
|
+
pytest
|
|
25
|
+
```
|
|
26
|
+
from the root directory. Currently coverage is only for features and parameters in active use. More tests to come.
|
|
27
|
+
|
|
28
|
+
## License
|
|
29
|
+
BSD-3 clause. see `LICENSE` file in this directory.
|
|
@@ -5,9 +5,9 @@ dependencies:
|
|
|
5
5
|
- python>=3.13
|
|
6
6
|
- pip
|
|
7
7
|
- "libblas=*=*mkl"
|
|
8
|
-
- numpy
|
|
8
|
+
- numpy
|
|
9
9
|
- matplotlib=3.*
|
|
10
|
-
- numba=0.
|
|
10
|
+
- numba=0.62.* # version pinned for galois 0.4.8
|
|
11
11
|
- pytest
|
|
12
12
|
- sphinx=7.*
|
|
13
13
|
- sphinx_rtd_theme
|
|
@@ -16,6 +16,6 @@ dependencies:
|
|
|
16
16
|
- ipykernel
|
|
17
17
|
- twine
|
|
18
18
|
- pip:
|
|
19
|
-
- galois>=0.4.
|
|
19
|
+
- galois>=0.4.8
|
|
20
20
|
- build
|
|
21
21
|
|
|
@@ -4,12 +4,12 @@ channels:
|
|
|
4
4
|
dependencies:
|
|
5
5
|
- python>=3.13
|
|
6
6
|
- pip
|
|
7
|
-
- numpy=2.2.6
|
|
8
|
-
- numba=0.
|
|
7
|
+
- numpy=2.2.6
|
|
8
|
+
- numba=0.62.* # version pinned for galois 0.4.8
|
|
9
9
|
- pytest
|
|
10
10
|
- sphinx=7.*
|
|
11
11
|
- sphinx_rtd_theme
|
|
12
12
|
- twine
|
|
13
13
|
- pip:
|
|
14
|
-
- galois>=0.4.
|
|
14
|
+
- galois>=0.4.8
|
|
15
15
|
- build
|
|
@@ -1,35 +1,34 @@
|
|
|
1
|
-
[project]
|
|
2
|
-
name = "syncoder"
|
|
3
|
-
dynamic = ["version"]
|
|
4
|
-
dependencies = [
|
|
5
|
-
"numpy>=
|
|
6
|
-
"galois>=0.4"
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
license = "
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
[
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
[
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
[
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
[tool.hatch.version]
|
|
1
|
+
[project]
|
|
2
|
+
name = "syncoder"
|
|
3
|
+
dynamic = ["version"]
|
|
4
|
+
dependencies = [
|
|
5
|
+
"numpy>=2",
|
|
6
|
+
"galois>=0.4.8"
|
|
7
|
+
]
|
|
8
|
+
description = "A codec for DNA data storage"
|
|
9
|
+
authors = [
|
|
10
|
+
{ name="Christopher Takahashi", email="cnt@uw.edu" }
|
|
11
|
+
]
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
requires-python = ">=3.11"
|
|
14
|
+
license = "BSD-3-Clause"
|
|
15
|
+
license-files = ["LICENSE"]
|
|
16
|
+
|
|
17
|
+
[project.urls]
|
|
18
|
+
Homepage = "https://example.com/TBD"
|
|
19
|
+
Issues = "https://example.com/TBD/issues"
|
|
20
|
+
|
|
21
|
+
[tool.setuptools]
|
|
22
|
+
packages = ["syncoder"]
|
|
23
|
+
|
|
24
|
+
[build-system]
|
|
25
|
+
requires = ["hatchling >= 1.26"]
|
|
26
|
+
build-backend = "hatchling.build"
|
|
27
|
+
|
|
28
|
+
[tool.pytest.ini_options]
|
|
29
|
+
testpaths = [
|
|
30
|
+
"tests"
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[tool.hatch.version]
|
|
35
34
|
path = "src/syncoder/__init__.py"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
from .codec import *
|
|
2
|
-
from .codec import _default_b32_alphabet, _default_b32_alphabet_alt
|
|
3
|
-
default_b32_alphabet = _default_b32_alphabet
|
|
4
|
-
default_b32_alphabet_alt = _default_b32_alphabet_alt
|
|
5
|
-
|
|
6
|
-
__version__ = "0.0.
|
|
1
|
+
from .codec import *
|
|
2
|
+
from .codec import _default_b32_alphabet, _default_b32_alphabet_alt
|
|
3
|
+
default_b32_alphabet = _default_b32_alphabet
|
|
4
|
+
default_b32_alphabet_alt = _default_b32_alphabet_alt
|
|
5
|
+
|
|
6
|
+
__version__ = "0.0.6a1"
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import struct
|
|
3
|
-
import itertools
|
|
4
1
|
import logging
|
|
5
2
|
import typing
|
|
3
|
+
import json
|
|
4
|
+
from . import sector01
|
|
6
5
|
from typing import Optional,Union, Callable, Tuple, Literal, cast #requires python 3.8 or later
|
|
7
6
|
|
|
8
7
|
|
|
@@ -11,17 +10,7 @@ from numpy.typing import ArrayLike
|
|
|
11
10
|
|
|
12
11
|
import galois
|
|
13
12
|
|
|
14
|
-
#import a version of reedsolo that supports multi instance
|
|
15
|
-
import importlib.metadata
|
|
16
|
-
import packaging.version
|
|
17
|
-
rsvers = importlib.metadata.version('reedsolo')
|
|
18
|
-
if packaging.version.Version(rsvers) <= packaging.version.Version("2.1.3b1"):
|
|
19
|
-
from . import reedsoloMulti as reedsolo
|
|
20
|
-
else:
|
|
21
|
-
import reedsolo
|
|
22
|
-
|
|
23
13
|
#### ROADMAP ####
|
|
24
|
-
# - integrate the baseN to DNA function here. (generalize from b32fn)
|
|
25
14
|
# - Strand prefix sub-encoder.
|
|
26
15
|
# - Sample objective functions (at least minimize homopolymers and avoid annealing.)
|
|
27
16
|
|
|
@@ -126,7 +115,8 @@ class BaseNBlockCodec:
|
|
|
126
115
|
n_strands:int=100,
|
|
127
116
|
max_strand_index:Optional[int]=None,
|
|
128
117
|
index_type:Literal["binary", "inner symbol", "inner"]="binary",
|
|
129
|
-
index_location:Literal["middle", "beginning", "end"]="middle"
|
|
118
|
+
index_location:Literal["middle", "beginning", "end"]="middle",
|
|
119
|
+
lazy_outer:bool = False):
|
|
130
120
|
"""
|
|
131
121
|
Initialize the Codec object.
|
|
132
122
|
|
|
@@ -138,15 +128,15 @@ class BaseNBlockCodec:
|
|
|
138
128
|
n_strands (int): Number of "strands" stored by this codec including redundant strands.
|
|
139
129
|
max_strand_index (Optional[int]): Maximum guaranteed index. Default: n_strands.
|
|
140
130
|
index_type (str): Type of index to use. Options: "binary", "inner symbol", "inner". Default: "binary".
|
|
131
|
+
index_type "binary" integrates the index into the data stream as a binary number.
|
|
132
|
+
This may help packing efficiency.
|
|
133
|
+
index_type "inner symbol"/"inner" integrates the index inner code symbol symbols.
|
|
134
|
+
This may help packing efficiency and always simplifies index extraction.
|
|
141
135
|
index_location (str): Location of the index within the inner code. Options: "middle", "beginning", "end". Default: "middle".
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
This may help packing efficiency and always simplifies index extraction.
|
|
147
|
-
index_location "middle" (default) places the index after the data symbols but before the redundancy symbols.
|
|
148
|
-
index_location "beginning" places the index before the data symbols.
|
|
149
|
-
index_location "end" places the index after the redundancy symbols. (not implemented)
|
|
136
|
+
index_location "middle" (default) places the index after the data symbols but before the redundancy symbols.
|
|
137
|
+
index_location "beginning" places the index before the data symbols.
|
|
138
|
+
index_location "end" places the index after the redundancy symbols. (not implemented)
|
|
139
|
+
lazy_outer (bool): If True, the outer code will be initialized lazily. Default: False.
|
|
150
140
|
"""
|
|
151
141
|
if n_strands<=255:
|
|
152
142
|
self.outer_alphabet_size_bytes = 1
|
|
@@ -159,6 +149,7 @@ class BaseNBlockCodec:
|
|
|
159
149
|
max_strand_index = n_strands
|
|
160
150
|
if max_strand_index<n_strands:
|
|
161
151
|
raise ValueError("max_strand_index must be greater than or equal to n_strands.")
|
|
152
|
+
self.max_strand_index = max_strand_index
|
|
162
153
|
|
|
163
154
|
if index_type not in {"binary","inner symbol","inner"}:
|
|
164
155
|
raise ValueError("index_type must be 'binary' or 'inner symbol'.")
|
|
@@ -183,6 +174,7 @@ class BaseNBlockCodec:
|
|
|
183
174
|
# make sure extra sequence numbers are not added to inner blocks. and or maybe add a
|
|
184
175
|
# parameter for the number of inner blocks per strand.
|
|
185
176
|
assert inner_n<inner_alphabet_size
|
|
177
|
+
self.inner_n = inner_n
|
|
186
178
|
|
|
187
179
|
self.inner_coder = galois.ReedSolomon(n=inner_alphabet_size-1,d=inner_d,field=galois.GF(inner_alphabet_size))
|
|
188
180
|
# in units of symbols (there are q=inner_alphabet_size possible symbols)
|
|
@@ -199,12 +191,12 @@ class BaseNBlockCodec:
|
|
|
199
191
|
self.inner_k = k
|
|
200
192
|
q = self.inner_coder.field.order
|
|
201
193
|
|
|
202
|
-
#number of bytes needed to store the index
|
|
203
|
-
self.index_bits = np.ceil(np.log2(max_strand_index)).astype(int)
|
|
204
|
-
logging.debug("bits per strand used for indexing: {}".format(self.index_bits))
|
|
205
194
|
|
|
206
195
|
#message length in bytes (without index)
|
|
207
196
|
if self.index_type == "binary":
|
|
197
|
+
#number of bytes needed to store the index
|
|
198
|
+
self.index_bits = np.ceil(np.log2(max_strand_index)).astype(int)
|
|
199
|
+
logging.debug("bits per strand used for indexing: {}".format(self.index_bits))
|
|
208
200
|
self.data_chunk_size = int(k*np.log2(q)-self.index_bits) // 8
|
|
209
201
|
elif self.index_type == "inner":
|
|
210
202
|
self.index_symbols = len(_int_to_baseN(max_strand_index,q))
|
|
@@ -222,7 +214,20 @@ class BaseNBlockCodec:
|
|
|
222
214
|
logging.debug( "codec message size: {} bytes".format(self.block_capacity_bytes) )
|
|
223
215
|
|
|
224
216
|
#init the outer coder
|
|
225
|
-
self.
|
|
217
|
+
if self.outer_alphabet_size_bytes==2 and self.data_chunk_size%2!=0:
|
|
218
|
+
raise ValueError("data_chunk_size must be even for 16 bit outer code.")
|
|
219
|
+
#c=0 to match reedsolo library's default for code compatibility with prior versions of this codec.
|
|
220
|
+
# This may be changed in a future major version.
|
|
221
|
+
|
|
222
|
+
self.outer_d = n_redundant_strands+1
|
|
223
|
+
self.outer_field = galois.GF(2**(self.outer_alphabet_size_bytes*8))
|
|
224
|
+
if lazy_outer:
|
|
225
|
+
self.outer_coder_fast = None
|
|
226
|
+
else:
|
|
227
|
+
self.outer_coder_fast = galois.ReedSolomon(n=2**(self.outer_alphabet_size_bytes*8)-1,
|
|
228
|
+
d=self.outer_d,
|
|
229
|
+
c=0,
|
|
230
|
+
field=self.outer_field)
|
|
226
231
|
|
|
227
232
|
def __compute_waste(self):
|
|
228
233
|
k = self.inner_k
|
|
@@ -243,6 +248,77 @@ class BaseNBlockCodec:
|
|
|
243
248
|
else:
|
|
244
249
|
logging.debug("{} symbols wasted per strand".format( _wasted_symbols))
|
|
245
250
|
|
|
251
|
+
def generate_sector1(self, alphabets,vendor_id = "UNKNOWN", primer_len=40, file_meta_data=None):
|
|
252
|
+
#change to lazy imports when python 3.15 is common.
|
|
253
|
+
#these don't take particularly long to import but i don't want to bloat things more.
|
|
254
|
+
import sys
|
|
255
|
+
import uuid
|
|
256
|
+
import json
|
|
257
|
+
from collections import OrderedDict
|
|
258
|
+
|
|
259
|
+
alen = len(alphabets[0])
|
|
260
|
+
abases = len(alphabets[0][0])
|
|
261
|
+
|
|
262
|
+
for a in alphabets:
|
|
263
|
+
if len(a) != alen:
|
|
264
|
+
raise ValueError("All alphabets must be of the same length")
|
|
265
|
+
for s in a:
|
|
266
|
+
if len(s) != abases:
|
|
267
|
+
raise ValueError("All elements of alphabets must be the same number of bases")
|
|
268
|
+
|
|
269
|
+
files_nstrands = 0
|
|
270
|
+
if file_meta_data is not None:
|
|
271
|
+
for block_name, block_meta in file_meta_data.items():
|
|
272
|
+
strand_count_code_block = block_meta["blen"]//self.data_chunk_size+self.outer_d-1
|
|
273
|
+
strand_count = strand_count_code_block*len(block_meta["istart"])
|
|
274
|
+
files_nstrands+=strand_count
|
|
275
|
+
|
|
276
|
+
#encode as one string to save space.
|
|
277
|
+
alphabets = [" ".join(x) for x in alphabets]
|
|
278
|
+
|
|
279
|
+
sector1 = OrderedDict(sector01.get_sector_1_obj())
|
|
280
|
+
#reserve octets 4-5 for the number of strands in sector 1.
|
|
281
|
+
u = bytearray(uuid.uuid4().bytes)
|
|
282
|
+
u[6] = 8<<4
|
|
283
|
+
u[8] = (u[8] & 0b00111111) | 0b10000000
|
|
284
|
+
u[4] = 0
|
|
285
|
+
u[5] = 0
|
|
286
|
+
sector1["id"] = str(uuid.UUID(bytes=bytes(u) ))
|
|
287
|
+
sector1["codec"]["ver"] = sys.modules['.'.join(__name__.split('.')[:-1])].__version__
|
|
288
|
+
codec_params = sector1["codec"]["params"]
|
|
289
|
+
codec_params["alphs"] = alphabets
|
|
290
|
+
codec_params["innerd"] = self.inner_coder.d
|
|
291
|
+
codec_params["innern"] = self.inner_n
|
|
292
|
+
codec_params["nstrands"] = self.n_strands
|
|
293
|
+
codec_params["neccstrand"] = self.outer_d-1
|
|
294
|
+
codec_params["imax"] = self.max_strand_index
|
|
295
|
+
codec_params["itype"] = self.index_type
|
|
296
|
+
codec_params["ilocation"] = self.index_location
|
|
297
|
+
sector1["vendorid"] = vendor_id
|
|
298
|
+
seq_params = sector1["seq"]
|
|
299
|
+
osize = self.inner_n*abases+primer_len
|
|
300
|
+
seq_params["osizemin"] = osize
|
|
301
|
+
seq_params["osizemedian"] = osize
|
|
302
|
+
seq_params["osizemax"] = osize
|
|
303
|
+
seq_params["ocount"] = files_nstrands
|
|
304
|
+
#these two don't make sense
|
|
305
|
+
seq_params["orepmin"] = 1
|
|
306
|
+
seq_params["orepmax"] = 100
|
|
307
|
+
|
|
308
|
+
if file_meta_data is not None:
|
|
309
|
+
sector1["addl"] = {"fmeta": file_meta_data}
|
|
310
|
+
|
|
311
|
+
sector1.move_to_end("id",last=False) #guarentee this is in the first code block.
|
|
312
|
+
#add this in when file meta is present.
|
|
313
|
+
#s1_len = len(json.dumps(sector1,separators=(',', ':')).encode('utf-8'))
|
|
314
|
+
#s1_strands = s1_len//self.data_chunk_size + (1 if s1_len%self.data_chunk_size>0 else 0)
|
|
315
|
+
#s1_redundant_strands = 50*(s1_len//self.block_capacity_bytes + (1 if s1_len%self.block_capacity_bytes>0 else 0))
|
|
316
|
+
#s1_strands += s1_redundant_strands
|
|
317
|
+
|
|
318
|
+
#sector1["id"] = uid+f":{s1_strands:05d}"
|
|
319
|
+
|
|
320
|
+
return sector1
|
|
321
|
+
|
|
246
322
|
def encode(self,data:bytes,index_start:int=0)->list[list[int]]:
|
|
247
323
|
""" Encodes the given data using the outer and inner codes.
|
|
248
324
|
|
|
@@ -290,14 +366,21 @@ class BaseNBlockCodec:
|
|
|
290
366
|
# ...
|
|
291
367
|
# |<rrrrrrrrrrrrrrrrr self.data_chunk_size rrrrrrrrrrrrraaaRRRRRRRR>|
|
|
292
368
|
|
|
369
|
+
#lazy init outer code
|
|
370
|
+
if self.outer_coder_fast is None:
|
|
371
|
+
self.outer_coder_fast = galois.ReedSolomon(n=2**(self.outer_alphabet_size_bytes*8)-1,
|
|
372
|
+
d=self.outer_d,
|
|
373
|
+
c=0,
|
|
374
|
+
field=self.outer_field)
|
|
375
|
+
|
|
293
376
|
#1. Reshape data
|
|
294
377
|
logging.debug("applying outer code to {} bytes".format(len(data)))
|
|
295
378
|
# reshape data so columns can be coded independently.
|
|
296
|
-
if self.
|
|
379
|
+
if self.outer_alphabet_size_bytes==1:
|
|
297
380
|
data_np = np.frombuffer(data,dtype=np.uint8)
|
|
298
381
|
assert len(data_np)%self.data_chunk_size == 0
|
|
299
382
|
data_np = data_np.reshape(-1,self.data_chunk_size)
|
|
300
|
-
elif self.
|
|
383
|
+
elif self.outer_alphabet_size_bytes==2:
|
|
301
384
|
data_np = np.frombuffer(data,dtype=np.uint16)
|
|
302
385
|
if self.data_chunk_size%2!=0:
|
|
303
386
|
raise ValueError("data_chunk_size must be even for 16 bit outer code.")
|
|
@@ -309,7 +392,8 @@ class BaseNBlockCodec:
|
|
|
309
392
|
raise ValueError("Unsupported outer code byte size.")
|
|
310
393
|
|
|
311
394
|
#2. apply the outer code (encode columns of data)
|
|
312
|
-
data_enc_cols = np.array([self.
|
|
395
|
+
data_enc_cols = np.array([self.outer_coder_fast.encode(dc) for dc in data_np.transpose()],dtype=data_np.dtype)
|
|
396
|
+
|
|
313
397
|
data_enc_chunks = data_enc_cols.transpose()
|
|
314
398
|
|
|
315
399
|
logging.debug("outer coded strands: {}".format(data_enc_chunks.shape[0]))
|
|
@@ -347,16 +431,17 @@ class BaseNBlockCodec:
|
|
|
347
431
|
|
|
348
432
|
return ic_chunks
|
|
349
433
|
|
|
350
|
-
def decode(self, data: list[list[int]], index_start: int = 0) -> Tuple[bytes,ArrayLike,ArrayLike,ArrayLike]:
|
|
434
|
+
def decode(self, data: list[list[int]], index_start: int = 0, n_strands: int|None = None) -> Tuple[bytes|None,ArrayLike,ArrayLike,ArrayLike]:
|
|
351
435
|
"""
|
|
352
436
|
Decodes the given data.
|
|
353
437
|
|
|
354
438
|
Args:
|
|
355
439
|
data (list[list[int]]): The encoded data to be decoded.
|
|
356
440
|
index_start (int, optional): The starting index for decoding. Defaults to 0.
|
|
441
|
+
n_strands (int, optional): The number of strands expected. Defaults to n_strands set in constructor.
|
|
357
442
|
|
|
358
443
|
Returns:
|
|
359
|
-
bytes: The decoded data.
|
|
444
|
+
bytes|None: The decoded data. Or None if decoding failed.
|
|
360
445
|
outer erasures: list of erasures in outer code.
|
|
361
446
|
outer errors: list of errors in outer code.
|
|
362
447
|
inner errors: list of number of errors in inner code by strand index. -1 -> strand not present.
|
|
@@ -364,17 +449,31 @@ class BaseNBlockCodec:
|
|
|
364
449
|
Raises:
|
|
365
450
|
None
|
|
366
451
|
"""
|
|
452
|
+
|
|
453
|
+
#lazy init outer code
|
|
454
|
+
if self.outer_coder_fast is None:
|
|
455
|
+
self.outer_coder_fast = galois.ReedSolomon(n=2**(self.outer_alphabet_size_bytes*8)-1,
|
|
456
|
+
d=self.outer_d,
|
|
457
|
+
c=0,
|
|
458
|
+
field=self.outer_field)
|
|
459
|
+
|
|
460
|
+
if n_strands is None:
|
|
461
|
+
n_strands = self.n_strands
|
|
462
|
+
|
|
367
463
|
# inner decode
|
|
368
464
|
## TODO: handle erasures in inner code.
|
|
369
465
|
|
|
370
466
|
# I'm sure it'll be a (FieldArray,int) ----------------V
|
|
371
|
-
chunked_data:list[Tuple[galois.FieldArray,int]] = [self.inner_coder.decode(d,errors=True) for d in data] #type:ignore
|
|
467
|
+
chunked_data:list[Tuple[galois.FieldArray,int]] = [self.inner_coder.decode(d,errors=True) for d in data if d != []] #type:ignore
|
|
372
468
|
|
|
373
469
|
|
|
374
470
|
# handle outer decode
|
|
375
|
-
ordered_chunks:list[bytes|None] = [None] *
|
|
376
|
-
chunk_errors:list[int] = [-1] *
|
|
471
|
+
ordered_chunks:list[bytes|None] = [None] * n_strands
|
|
472
|
+
chunk_errors:list[int] = [-1] * n_strands
|
|
377
473
|
for chunk in chunked_data:
|
|
474
|
+
#TODO: decide if its better to throw these out or keep them.
|
|
475
|
+
#if chunk[1]<0: #can't decode and the address may be bad so discard.
|
|
476
|
+
# continue
|
|
378
477
|
if self.index_type == "binary":
|
|
379
478
|
chunk_int = _baseN_to_int(chunk[0].tolist(), self.inner_coder.field.order)
|
|
380
479
|
chunk_index = (chunk_int >> (self.data_chunk_size * 8)) - index_start
|
|
@@ -391,12 +490,16 @@ class BaseNBlockCodec:
|
|
|
391
490
|
raise NotImplementedError("index_location 'end' is not implemented.")
|
|
392
491
|
else:
|
|
393
492
|
raise ValueError("Unknown index type: {}".format(self.index_type)) #should be impossible to get here.
|
|
394
|
-
|
|
493
|
+
#this can be to big sometimes. TODO handle more gracefully
|
|
395
494
|
try:
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
495
|
+
chunk_bytes:bytes = chunk_int.to_bytes(self.data_chunk_size, 'little')
|
|
496
|
+
try:
|
|
497
|
+
ordered_chunks[chunk_index] = chunk_bytes
|
|
498
|
+
chunk_errors[chunk_index] = chunk[1]
|
|
499
|
+
except IndexError:
|
|
500
|
+
logging.info("strand index out of bounds:" + str(chunk_index))
|
|
501
|
+
except OverflowError:
|
|
502
|
+
logging.info("chunk overflow for strand index:" + str(chunk_index)+ "intval: "+ str(chunk_int))
|
|
400
503
|
|
|
401
504
|
# identify erasures
|
|
402
505
|
erasures = []
|
|
@@ -407,20 +510,32 @@ class BaseNBlockCodec:
|
|
|
407
510
|
#we're all bytes now, but the checker can't tell that.
|
|
408
511
|
ordered_chunks_bytes:list[bytes] = typing.cast(list[bytes],ordered_chunks)
|
|
409
512
|
|
|
410
|
-
|
|
513
|
+
# report erasures (lost strands) in outer code
|
|
514
|
+
logging.info("erasures (lost) chunks: {}".format(erasures))
|
|
515
|
+
|
|
516
|
+
if self.outer_alphabet_size_bytes==1:
|
|
411
517
|
outer_dtype = np.uint8
|
|
412
|
-
elif self.
|
|
518
|
+
elif self.outer_alphabet_size_bytes==2:
|
|
413
519
|
outer_dtype = np.uint16
|
|
414
520
|
else:
|
|
415
521
|
raise ValueError("Unsupported outer code byte size.")
|
|
416
|
-
data_np_encoded = np.array([np.frombuffer(c, dtype=outer_dtype) for c in ordered_chunks_bytes]).transpose()
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
522
|
+
data_np_encoded = np.array([np.frombuffer(c, dtype=outer_dtype) for c in ordered_chunks_bytes]).transpose()
|
|
523
|
+
|
|
524
|
+
has_erasuers = len(erasures)>0
|
|
525
|
+
erasures_bool = np.zeros(data_np_encoded.shape[1],dtype=bool)
|
|
526
|
+
if has_erasuers:
|
|
527
|
+
erasures_bool[erasures] = True
|
|
528
|
+
data_decoded_results = [self.outer_coder_fast.decode(dc,errors=True,erasures=erasures_bool) for dc in data_np_encoded]
|
|
529
|
+
data_errata_col_pos = [] #galois doesn't report error positions
|
|
530
|
+
data_num_errors = [int(x[1]) for x in data_decoded_results]
|
|
531
|
+
if -1 in data_num_errors:
|
|
532
|
+
logging.error("Outer code decode failed on at least one column.")
|
|
533
|
+
return None, erasures, data_num_errors, chunk_errors
|
|
534
|
+
#raise ValueError(f"Fast mode outer code decode failed on at least one column. numerrors:{data_num_errors}")
|
|
535
|
+
logging.info("Found {} outer code column errors.".format(data_num_errors))
|
|
536
|
+
data_decoded_chunks = np.array([np.array(d[0]) for d in data_decoded_results],dtype=outer_dtype).transpose()
|
|
420
537
|
|
|
421
|
-
|
|
422
|
-
logging.info("erasures (lost) chunks: {}".format(erasures))
|
|
423
|
-
data_errata_col_pos = [e[2] for e in data_decoded_results]
|
|
538
|
+
data_decoded = data_decoded_chunks.flatten().tobytes()
|
|
424
539
|
|
|
425
540
|
errors = []
|
|
426
541
|
for i, pos in enumerate(data_errata_col_pos):
|
|
@@ -434,20 +549,23 @@ class BaseNBlockCodec:
|
|
|
434
549
|
if e[0] in erasures:
|
|
435
550
|
pass
|
|
436
551
|
else:
|
|
437
|
-
logging.
|
|
552
|
+
logging.info("Outer code errors found on strand {}, byte {}.".format(*e))
|
|
553
|
+
|
|
554
|
+
return data_decoded, erasures, data_num_errors, chunk_errors # pyright: ignore[reportPossiblyUnboundVariable]
|
|
438
555
|
|
|
439
|
-
return data_decoded, erasures, errors, chunk_errors
|
|
440
556
|
|
|
441
|
-
def extract_index_dna(self, dna: str|bytes, words:list[str], alternate_words:list[str], error_check: bool = False, padding: Literal["left","right"]="right") -> int:
|
|
557
|
+
def extract_index_dna(self, dna: str|bytes, words:list[str], alternate_words:list[str], error_check: bool|Literal["erasures"] = False, padding: Literal["left","right"]="right") -> int:
|
|
442
558
|
"""
|
|
443
559
|
Extracts the index from the given DNA strand.
|
|
444
560
|
|
|
445
561
|
Args:
|
|
446
562
|
dna (str|bytes): The input DNA strand from which to extract the index.
|
|
447
|
-
error_check (bool, optional): If True, the inner_codec will be used to
|
|
448
|
-
attempt to check for errors.
|
|
449
|
-
|
|
450
|
-
|
|
563
|
+
error_check (bool|Literal["erasures"], optional): If True, the inner_codec will be used to
|
|
564
|
+
attempt to check for errors. if "erasures", the inner codec will check only if it encounters an N.
|
|
565
|
+
Defaults to False.
|
|
566
|
+
padding Literal["left","right"], optional): Assume any missing bases are left or right of index.
|
|
567
|
+
Defaults to "right".
|
|
568
|
+
Note: padded sequences are unlikely to error correct properly.
|
|
451
569
|
words (list[str]): The primary set of base words used, in order, for encoding.
|
|
452
570
|
alternate_words (list[str]): The alternate (aka synonymous) set of words used, in order, for encoding.
|
|
453
571
|
|
|
@@ -466,12 +584,30 @@ class BaseNBlockCodec:
|
|
|
466
584
|
elif padding == "left":
|
|
467
585
|
dna_s = "A"*num_missing + dna_s
|
|
468
586
|
|
|
587
|
+
|
|
588
|
+
if error_check == "erasures":
|
|
589
|
+
erasures = np.zeros(len(dna_s)//symbol_len,dtype=bool)
|
|
590
|
+
_temp = []
|
|
591
|
+
for i,c in enumerate(dna_s):
|
|
592
|
+
if c.upper() not in "ACGT":
|
|
593
|
+
erasures[i//symbol_len] = True
|
|
594
|
+
_temp.append("A")
|
|
595
|
+
else:
|
|
596
|
+
_temp.append(c)
|
|
597
|
+
has_erasures = bool(np.any(erasures))
|
|
598
|
+
if has_erasures: #don't bother if nothing has changed.
|
|
599
|
+
dna_s = "".join(_temp)
|
|
600
|
+
else:
|
|
601
|
+
has_erasures = False
|
|
602
|
+
erasures = None
|
|
603
|
+
|
|
604
|
+
ec = (has_erasures and error_check=="erasures") or (error_check==True)
|
|
469
605
|
#This could fail if padding makes a symbol not in words or alternate_words.
|
|
470
606
|
data = dna_to_bN([dna_s],words,alternate_words)[0]
|
|
471
607
|
|
|
472
|
-
return self.extract_index(data,
|
|
608
|
+
return self.extract_index(data,ec,erasures)
|
|
473
609
|
|
|
474
|
-
def extract_index(self, data: list[int], error_check: bool = False) -> int:
|
|
610
|
+
def extract_index(self, data: list[int], error_check: bool = False, erasures: np.ndarray|None = None) -> int:
|
|
475
611
|
"""
|
|
476
612
|
Extracts the index from the given inner code sequence.
|
|
477
613
|
|
|
@@ -480,6 +616,7 @@ class BaseNBlockCodec:
|
|
|
480
616
|
If a list[int] is given, it is assumed be be inner code symbols.
|
|
481
617
|
error_check (bool, optional): If True, the inner_codec will be used to
|
|
482
618
|
attempt to check for errors. Defaults to False.
|
|
619
|
+
erasures (np.ndarray|None, optional): A boolean array indicating which positions are erased. Defaults to None.
|
|
483
620
|
|
|
484
621
|
Returns:
|
|
485
622
|
int: The index. or -1 if error_check==True and unrecoverable error found.
|
|
@@ -487,7 +624,7 @@ class BaseNBlockCodec:
|
|
|
487
624
|
if error_check == False:
|
|
488
625
|
data = data[0:self.inner_k] #extract message
|
|
489
626
|
else:
|
|
490
|
-
data_gf, n_corrected = self.inner_coder.decode(data,errors=True)
|
|
627
|
+
data_gf, n_corrected = self.inner_coder.decode(data,errors=True,erasures=erasures) #type:ignore
|
|
491
628
|
data = data_gf.tolist()
|
|
492
629
|
if n_corrected < 0:
|
|
493
630
|
return -1
|
|
@@ -549,7 +686,7 @@ def longest_binder(a:str,b:str):
|
|
|
549
686
|
|
|
550
687
|
|
|
551
688
|
|
|
552
|
-
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]]:
|
|
689
|
+
def b32_to_DNA_optimize(file_data:list[ArrayLike],words:list[str], alternate_words:list[str], mask:Union[ArrayLike,None]=None, nmasked:int =-1, penalty_fn:Union[Callable[[str],int],None]=None)->list[Tuple[bytes,int]]:
|
|
553
690
|
"""
|
|
554
691
|
TODO: rename to bN_to_DNA_optimize, and add test.
|
|
555
692
|
file_data: 2d list of integral types (symbols)
|
|
@@ -810,3 +947,79 @@ def remove_bytes(data:bytes, insert:int|bytes, chunk_len:int, num_inserts:int) -
|
|
|
810
947
|
del data[ins_index:ins_index + insert_len]
|
|
811
948
|
return bytes(data)
|
|
812
949
|
|
|
950
|
+
|
|
951
|
+
########################
|
|
952
|
+
# Sector 0 and 1 funcs #
|
|
953
|
+
########################
|
|
954
|
+
|
|
955
|
+
|
|
956
|
+
def sector_1_codec():
|
|
957
|
+
"""Returns a BaseNBlockCodec object with parameters suitable for encoding sector 1 of the SyNCODER format."""
|
|
958
|
+
return BaseNBlockCodec(inner_alphabet_size=32,
|
|
959
|
+
inner_d=5,
|
|
960
|
+
inner_n=70//3,
|
|
961
|
+
n_strands=255,
|
|
962
|
+
n_redundant_strands=50,
|
|
963
|
+
max_strand_index=2**(5*3)-1,#32,767 strands max.
|
|
964
|
+
index_type="inner")
|
|
965
|
+
|
|
966
|
+
def generate_sector1(codec:BaseNBlockCodec,alphabets:list[list[str]],vendor_id:str, file_meta_data:dict, primer_len:int=40, length:int=-1) -> list[bytes]:
|
|
967
|
+
"""Generates the sector 1 object for the SyNCODER format.
|
|
968
|
+
|
|
969
|
+
Args:
|
|
970
|
+
alphabets (list of list of str): A list of alphabets, where each alphabet is a list of strings representing the symbols in that alphabet.
|
|
971
|
+
vendor_id (str): A DNA string representing the vendor ID.
|
|
972
|
+
file_meta_data (dict): A dictionary containing metadata about the files being encoded.
|
|
973
|
+
primer_len (int, optional): The length of the primers to be used in sector 1. Defaults to 40.
|
|
974
|
+
length (int, optional): The number of strands in the sector 1 object. If -1, the number of strands will be determined automatically. Defaults to -1.
|
|
975
|
+
|
|
976
|
+
Returns:
|
|
977
|
+
list[bytes]: A list of bytes representing the encoded sector 1 object.
|
|
978
|
+
"""
|
|
979
|
+
|
|
980
|
+
assert len(vendor_id) == 35 #required by spec.
|
|
981
|
+
s1codec = sector_1_codec()
|
|
982
|
+
sector1 = codec.generate_sector1(alphabets,vendor_id,primer_len,file_meta_data)
|
|
983
|
+
|
|
984
|
+
if length>=0:
|
|
985
|
+
nstarnds_ashex = hex(length)[2:].rjust(4,'0')
|
|
986
|
+
id_parts = sector1["id"].split("-")
|
|
987
|
+
id_parts[1] = nstarnds_ashex
|
|
988
|
+
sector1["id"] = "-".join(id_parts)
|
|
989
|
+
|
|
990
|
+
s1json = json.dumps(sector1,separators=(',', ':')).encode('utf-8')
|
|
991
|
+
|
|
992
|
+
padding = s1codec.data_chunk_size - (len(s1json) % s1codec.data_chunk_size)
|
|
993
|
+
if padding != s1codec.data_chunk_size:
|
|
994
|
+
# Distribute padding bytes by inserting at most one space after commas
|
|
995
|
+
new_chars = bytearray(b" ") #put one space at the start.
|
|
996
|
+
rem = padding-1
|
|
997
|
+
for c in s1json:
|
|
998
|
+
new_chars.append(c)
|
|
999
|
+
if c == ',' and rem > 0:
|
|
1000
|
+
new_chars.append(ord(' ')) # add a space after the comma
|
|
1001
|
+
rem -= 1
|
|
1002
|
+
if rem > 0: #append any remaining spaces at the end (probably wont happen)
|
|
1003
|
+
new_chars.extend(b" " * rem)
|
|
1004
|
+
s1json_padded = bytes(new_chars)
|
|
1005
|
+
assert json.loads(s1json_padded) == sector1, "Padded JSON does not match original data"
|
|
1006
|
+
s1json = s1json_padded
|
|
1007
|
+
|
|
1008
|
+
encoded_s1 = []
|
|
1009
|
+
cbstart = 0
|
|
1010
|
+
istart = 0
|
|
1011
|
+
while cbstart < len(s1json):
|
|
1012
|
+
encoded_s1_block= s1codec.encode(s1json[cbstart:(cbstart+s1codec.block_capacity_bytes)],index_start=istart)
|
|
1013
|
+
istart+=len(encoded_s1_block)
|
|
1014
|
+
cbstart+=s1codec.block_capacity_bytes
|
|
1015
|
+
encoded_s1 += encoded_s1_block
|
|
1016
|
+
|
|
1017
|
+
#reoncode sector 1 with updated id.
|
|
1018
|
+
if length<0:
|
|
1019
|
+
#update sector1 id with number of strands in s1.
|
|
1020
|
+
return generate_sector1(codec,alphabets,vendor_id,file_meta_data,primer_len,length=len(encoded_s1))
|
|
1021
|
+
|
|
1022
|
+
#convert to base32 pad with a random base to meet the 70 base requirement for sector 1.
|
|
1023
|
+
s1DNA = [x[0]+bytes(np.random.choice([b"A",b"C",b"T",b"G"])) for x in b32_to_DNA_optimize(encoded_s1,_default_b32_alphabet,_default_b32_alphabet_alt)]
|
|
1024
|
+
s1DNA_with_primers = [sector01.s1fp_ext.encode("utf-8") + x + sector01.s1rp_rc_ext.encode("utf-8") for x in s1DNA]
|
|
1025
|
+
return s1DNA_with_primers
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
s1fp = "ACAACGCTCAAATCAGGG"
|
|
4
|
+
s1rp = "GTGACGGTAGGTGAAATC"
|
|
5
|
+
s1fp_ext = "GCACAACGCTCAAATCAGGG"
|
|
6
|
+
s1rp_ext = "CGGTGACGGTAGGTGAAATC"
|
|
7
|
+
s1rp_rc = "GATTTCACCTACCGTCAC"
|
|
8
|
+
s1rp_rc_ext = "GATTTCACCTACCGTCACCG"
|
|
9
|
+
JSON_template_mandatory = \
|
|
10
|
+
"""
|
|
11
|
+
{
|
|
12
|
+
"id": null,
|
|
13
|
+
"seq": {
|
|
14
|
+
"osizemin": null,
|
|
15
|
+
"osizemedian": null,
|
|
16
|
+
"osizemax": null,
|
|
17
|
+
"orepmin": null,
|
|
18
|
+
"orepmax": null,
|
|
19
|
+
"ocount": null,
|
|
20
|
+
"adapter": false,
|
|
21
|
+
"natural": true,
|
|
22
|
+
"coverageinfo": "30x"
|
|
23
|
+
},
|
|
24
|
+
"codec": {
|
|
25
|
+
"id": "ATGTATAGCGCTACGAGCTGCAGTCATCTGATCTC",
|
|
26
|
+
"vendor": "UW MISL",
|
|
27
|
+
"name": "SyNCODER",
|
|
28
|
+
"ver": null,
|
|
29
|
+
"uri": "http://misl.bio",
|
|
30
|
+
"params": {}
|
|
31
|
+
},
|
|
32
|
+
"vendorid": null
|
|
33
|
+
}
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
JSON_template_optional = \
|
|
37
|
+
"""
|
|
38
|
+
{
|
|
39
|
+
"desc": null,
|
|
40
|
+
"hash": {
|
|
41
|
+
"alg": "SHA256",
|
|
42
|
+
"val": null,
|
|
43
|
+
"addl": {}
|
|
44
|
+
},
|
|
45
|
+
"ts": null,
|
|
46
|
+
"vendor": {
|
|
47
|
+
"name": "UW MISL",
|
|
48
|
+
"uri": "http://misl.bio",
|
|
49
|
+
"contact": null
|
|
50
|
+
},
|
|
51
|
+
"opt": {}
|
|
52
|
+
}
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def get_sector_1_obj():
|
|
56
|
+
return json.loads(JSON_template_mandatory)
|
|
57
|
+
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
#write a test for codec.py
|
|
2
|
-
|
|
3
1
|
import random
|
|
4
2
|
import numpy as np
|
|
5
3
|
from itertools import product
|
|
@@ -44,6 +42,32 @@ def encode_decode(index_type,index_loc:Literal["beginning","middle"]="middle"):
|
|
|
44
42
|
#check inner_n is causing the correct number of bases
|
|
45
43
|
assert len(coded[0]) == 30
|
|
46
44
|
|
|
45
|
+
def test_encode_decode_fast_erase():
|
|
46
|
+
index_type = "inner"
|
|
47
|
+
index_loc:Literal["beginning","middle"]="middle"
|
|
48
|
+
c = codec.BaseNBlockCodec(inner_alphabet_size=32,
|
|
49
|
+
inner_d=5,
|
|
50
|
+
inner_n=30,
|
|
51
|
+
index_type=index_type,
|
|
52
|
+
index_location=index_loc,
|
|
53
|
+
n_strands = 2001,
|
|
54
|
+
n_redundant_strands=100
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
in_text = np.random.bytes( c.block_capacity_bytes )
|
|
58
|
+
coded = c.encode( in_text )
|
|
59
|
+
dnadata = syncoder.b32_to_DNA(coded,syncoder._default_b32_alphabet,syncoder._default_b32_alphabet_alt)
|
|
60
|
+
|
|
61
|
+
random.shuffle(coded)
|
|
62
|
+
coded = coded[:-90] #erase a random strands
|
|
63
|
+
#corrupt_index = random.randint(0,len(coded[0]))
|
|
64
|
+
#coded[0][corrupt_index] = 6
|
|
65
|
+
|
|
66
|
+
out_text = c.decode( coded)[0]
|
|
67
|
+
assert in_text == out_text
|
|
68
|
+
#check inner_n is causing the correct number of bases
|
|
69
|
+
assert len(coded[0]) == 30
|
|
70
|
+
|
|
47
71
|
def test_coded_to_bases():
|
|
48
72
|
#TODO: test optimize too
|
|
49
73
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|