Skip to content

Commit ac9b272

Browse files
Created a test suite for image encoder.
1 parent 69641a4 commit ac9b272

File tree

10 files changed

+180
-36
lines changed

10 files changed

+180
-36
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ __pycache__/
66
.DS_Store
77

88
output/
9+
/my_scripts/

encoder/algorithms/xor_encoding.py

+8-9
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,24 @@
22

33
import numpy as np
44

5-
from encoder.bit_density import pad_bit_array, truncate_bit_array, convert_to_bit_density, convert_from_bit_density
5+
from encoder.bit_density import pad_bit_array, convert_to_bit_density, convert_from_bit_density
6+
from encoder.constants import BITS_PER_BYTE, BYTES_PER_UINT64
67
from encoder.utilities import add_length_info, strip_length_info
78

89

910
class XorEncoding:
10-
BITS_PER_BYTE = 8
11-
BYTES_PER_UINT64 = 8
1211

1312
def __init__(self, block_size, intensity):
14-
self.block_size = block_size
13+
self.bits_per_block = block_size
14+
self.block_size = 2 ** self.bits_per_block
1515
self.intensity = intensity
16-
self.bits_per_block = int(np.log2(self.block_size))
1716
self.block_template = np.arange(self.block_size, dtype=np.uint64)
1817

1918
def data_capacity(self, img_size):
20-
""" Return data capacity for given algorithm and image """
19+
""" Return data capacity for given algorithm and image -- in bytes """
2120
bits_in_image = img_size[0] * img_size[1] * img_size[2] * self.intensity
2221
number_of_blocks = bits_in_image // self.block_size
23-
return number_of_blocks * self.bits_per_block // self.BITS_PER_BYTE - self.BYTES_PER_UINT64
22+
return number_of_blocks * self.bits_per_block // BITS_PER_BYTE - BYTES_PER_UINT64
2423

2524
def _pack_data(self, data):
2625
"""
@@ -86,7 +85,7 @@ def encode(self, img_data, payload):
8685
initial_img_length = shape[0] * shape[1] * shape[2]
8786

8887
raw_data = img_data.flatten()
89-
raw_bits = np.unpackbits(raw_data).reshape(initial_img_length, self.BITS_PER_BYTE)
88+
raw_bits = np.unpackbits(raw_data).reshape(initial_img_length, BITS_PER_BYTE)
9089

9190
selected_raw_bits = raw_bits[:, -self.intensity:].flatten()
9291

@@ -109,7 +108,7 @@ def decode(self, img_data):
109108
initial_img_length = shape[0] * shape[1] * shape[2]
110109

111110
raw_data = img_data.flatten()
112-
raw_bits = np.unpackbits(raw_data).reshape(initial_img_length, self.BITS_PER_BYTE)
111+
raw_bits = np.unpackbits(raw_data).reshape(initial_img_length, BITS_PER_BYTE)
113112

114113
selected_raw_bits = raw_bits[:, -self.intensity:].flatten()
115114

encoder/bit_density.py

+6-20
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
import numpy as np
44

5+
from encoder.constants import BITS_PER_BYTE
6+
57

68
def truncate_bit_array(bit_array, bit_length):
7-
""" Truncate given bit-array (uint8), so that the length can be divided by bit_length """
9+
""" Truncate given bit-array (uint8), so that the length can be divided by bit_length without a remainder """
810
overflow = bit_array.shape[0] % bit_length
911

1012
if overflow > 0:
@@ -14,7 +16,7 @@ def truncate_bit_array(bit_array, bit_length):
1416

1517

1618
def pad_bit_array(bit_array, bit_length):
17-
""" Pad given bit-array (uint8) with zeros, so that the length can be divided by bit_length """
19+
""" Pad given bit-array (uint8) with zeros, so that the length can be divided by bit_length without a remainder """
1820
missing_bits = (bit_length - (bit_array.shape[0] % bit_length)) % bit_length
1921

2022
# PAD BITS IF NEEDED
@@ -40,28 +42,12 @@ def convert_to_bit_density(input_data, result_density):
4042

4143

4244
def convert_from_bit_density(input_data, result_density):
43-
""" Convert from given density to uint8 """
45+
""" Convert from given density (uint64) to uint8 """
4446
input_shape = input_data.shape[0]
4547
raw_bits = np.unpackbits(input_data.astype(np.dtype('>u8')).view(np.uint8)).reshape(input_shape, 64)
4648
truncated_bits = raw_bits[:, -result_density:]
4749
truncated_bits = truncated_bits.reshape(truncated_bits.shape[0] * truncated_bits.shape[1])
4850

49-
TARGET_DENSITY = 8
50-
51-
truncated_bits = pad_bit_array(truncated_bits, TARGET_DENSITY)
51+
truncated_bits = pad_bit_array(truncated_bits, BITS_PER_BYTE)
5252

5353
return np.packbits(truncated_bits)
54-
55-
56-
def main():
57-
input = np.array([0, 1, 255], dtype=np.uint8)
58-
converted = convert_to_bit_density(input, 12)
59-
and_back = convert_from_bit_density(converted, 12)
60-
61-
print('Input =', input)
62-
print('Converted =', converted)
63-
print('And back =', and_back)
64-
65-
66-
if __name__ == '__main__':
67-
main()

encoder/constants.py

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
__author__ = 'jrx'
2+
3+
BITS_PER_BYTE = 8
4+
BYTES_PER_UINT64 = 8

encoder/main.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,12 @@ def info_action(args):
2727
assert im.mode == 'RGB', 'Only RGB mode images are supported!'
2828
rgb_data = np.array(im)
2929

30-
3130
capacity = algorithm.data_capacity(rgb_data.shape)
32-
print(capacity)
3331

34-
# print('Data capacity {:.2f} b/{:.2f} kb/{:.2f} mb'.format(capacity, capacity / 1024, capacity / 1024 / 1024))
32+
if args.verbose:
33+
print('Data capacity {:.2f} b/{:.2f} kb/{:.2f} mb'.format(capacity, capacity / 1024, capacity / 1024 / 1024))
34+
else:
35+
print(capacity)
3536

3637

3738
def encode_action(args):
@@ -86,6 +87,7 @@ def main():
8687
parser.add_argument('-o', '--outfile', help='Specify output file', default='')
8788
parser.add_argument('-a', '--algorithm', help='Choose your algorithm', default='xor_encoding')
8889
parser.add_argument('-d', '--datafile', help='Specify data file', default='')
90+
parser.add_argument('-v', '--verbose', help='Make program more verbose', default='')
8991

9092
group = parser.add_argument_group('Algorithm parameters')
9193

scripts/demo.sh

+4-4
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ if ! [ -d output ]; then
55
fi
66

77
echo "Encoding marocco..."
8-
python3 -m encoder.main encode -i data/marocco.jpg -o output/marocco_encoded.png -d data/ALICE_IN_WONDERLAND --intensity=2 --block_size=64
8+
python3 -m encoder.main encode -i data/marocco.jpg -o output/marocco_encoded.png -d data/ALICE_IN_WONDERLAND --intensity=2 --block_size=6
99
echo "Decoding marocco..."
10-
python3 -m encoder.main decode -i output/marocco_encoded.png -o output/marocco_decoded.txt --intensity=2 --block_size=64
10+
python3 -m encoder.main decode -i output/marocco_encoded.png -o output/marocco_decoded.txt --intensity=2 --block_size=6
1111

1212
echo "Encoding dragon..."
13-
python3 -m encoder.main encode -i data/dragon.jpg -o output/dragon_encoded.png -d data/ALICE_IN_WONDERLAND --intensity=4 --block_size=8
13+
python3 -m encoder.main encode -i data/dragon.jpg -o output/dragon_encoded.png -d data/ALICE_IN_WONDERLAND --intensity=4 --block_size=3
1414
echo "Decoding dragon..."
15-
python3 -m encoder.main decode -i output/dragon_encoded.png -o output/dragon_decoded.txt --intensity=4 --block_size=8
15+
python3 -m encoder.main decode -i output/dragon_encoded.png -o output/dragon_decoded.txt --intensity=4 --block_size=3
1616

1717
echo "Done"

tests/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__author__ = 'jrx'

tests/test_bit_density.py

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
__author__ = 'jrx'
2+
3+
4+
import numpy as np
5+
6+
from numpy.testing import assert_array_equal
7+
from nose.tools import assert_equal
8+
9+
10+
from encoder.bit_density import convert_from_bit_density, convert_to_bit_density
11+
12+
13+
def test_small_input():
14+
initial = np.array([0, 1, 255], dtype=np.uint8)
15+
converted = convert_to_bit_density(initial, 12)
16+
and_back = convert_from_bit_density(converted, 12)
17+
18+
assert_array_equal(initial, and_back)
19+
20+
21+
def test_random_input():
22+
for i in range(10):
23+
random_input = np.random.random_integers(0, 255, np.random.randint(10, 100)).astype(np.uint8)
24+
random_bit_density = np.random.randint(2, 20)
25+
26+
converted = convert_to_bit_density(random_input, random_bit_density)
27+
and_back = convert_from_bit_density(converted, random_bit_density)
28+
29+
assert_equal(converted.dtype, np.uint64)
30+
assert_equal(and_back.dtype, np.uint8)
31+
32+
assert_array_equal(random_input, and_back[:len(random_input)])
33+
34+
35+
def test_fixed_cases():
36+
initial = np.array([0, 1, 255], dtype=np.uint8)
37+
converted = convert_to_bit_density(initial, 12)
38+
assert_array_equal(converted, np.array([0, 511], dtype=np.uint64))
39+
40+

tests/test_utilities.py

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
__author__ = 'jrx'
2+
3+
import numpy as np
4+
5+
from numpy.testing import assert_array_equal
6+
from nose.tools import assert_equal
7+
8+
9+
from encoder.bit_density import convert_from_bit_density, convert_to_bit_density
10+
from encoder.utilities import add_length_info, strip_length_info
11+
12+
13+
def test_random_input_density_conversion():
14+
for i in range(10):
15+
random_input = np.random.random_integers(0, 255, np.random.randint(10, 100)).astype(np.uint8)
16+
random_bit_density = np.random.randint(2, 20)
17+
18+
converted = convert_to_bit_density(add_length_info(random_input), random_bit_density)
19+
and_back = strip_length_info(convert_from_bit_density(converted, random_bit_density))
20+
21+
assert_equal(converted.dtype, np.uint64)
22+
assert_equal(and_back.dtype, np.uint8)
23+
24+
assert_array_equal(random_input, and_back)

tests/test_xor_encoder.py

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
__author__ = 'jrx'
2+
3+
4+
import numpy as np
5+
6+
from numpy.testing import assert_array_equal
7+
from nose.tools import assert_greater, assert_equal
8+
9+
10+
from encoder.algorithms.xor_encoding import XorEncoding
11+
12+
13+
def test_capacity_monotonic():
14+
shape = (640, 480, 3)
15+
16+
base = XorEncoding(block_size=6, intensity=4)
17+
low_block = XorEncoding(block_size=5, intensity=4)
18+
high_intensity = XorEncoding(block_size=5, intensity=8)
19+
20+
assert_greater(low_block.data_capacity(shape), base.data_capacity(shape))
21+
assert_greater(high_intensity.data_capacity(shape), base.data_capacity(shape))
22+
23+
assert_equal(base.data_capacity(shape), 43192)
24+
assert_equal(low_block.data_capacity(shape), 71992)
25+
assert_equal(high_intensity.data_capacity(shape), 143992)
26+
27+
28+
def test_base():
29+
image = np.random.randint(0, 256, (640, 480, 3)).astype(np.uint8)
30+
encoding = XorEncoding(block_size=6, intensity=4)
31+
max_size = encoding.data_capacity(image.shape)
32+
33+
data = np.random.randint(0, 256, max_size).astype(np.uint8)
34+
35+
encoded = encoding.encode(image, data)
36+
37+
assert_equal(encoded.dtype, np.uint8)
38+
assert_equal(encoded.shape, image.shape)
39+
40+
decoded = encoding.decode(encoded)
41+
42+
assert_equal(decoded.dtype, np.uint8)
43+
assert_equal(decoded.shape, data.shape)
44+
45+
assert_array_equal(data, decoded)
46+
47+
48+
def test_high_intensity():
49+
image = np.random.randint(0, 256, (640, 480, 3)).astype(np.uint8)
50+
encoding = XorEncoding(block_size=6, intensity=8)
51+
max_size = encoding.data_capacity(image.shape)
52+
53+
data = np.random.randint(0, 256, max_size).astype(np.uint8)
54+
55+
encoded = encoding.encode(image, data)
56+
57+
assert_equal(encoded.dtype, np.uint8)
58+
assert_equal(encoded.shape, image.shape)
59+
60+
decoded = encoding.decode(encoded)
61+
62+
assert_equal(decoded.dtype, np.uint8)
63+
assert_equal(decoded.shape, data.shape)
64+
65+
assert_array_equal(data, decoded)
66+
67+
68+
def test_low_block():
69+
image = np.random.randint(0, 256, (640, 480, 3)).astype(np.uint8)
70+
encoding = XorEncoding(block_size=1, intensity=1)
71+
max_size = encoding.data_capacity(image.shape)
72+
73+
data = np.random.randint(0, 256, max_size).astype(np.uint8)
74+
75+
encoded = encoding.encode(image, data)
76+
77+
assert_equal(encoded.dtype, np.uint8)
78+
assert_equal(encoded.shape, image.shape)
79+
80+
decoded = encoding.decode(encoded)
81+
82+
assert_equal(decoded.dtype, np.uint8)
83+
assert_equal(decoded.shape, data.shape)
84+
85+
assert_array_equal(data, decoded)
86+
87+

0 commit comments

Comments
 (0)