mirror of
https://github.com/dnet/pySSTV.git
synced 2025-12-06 07:12:00 +01:00
commit
f14d0a8e66
3
requirements.txt
Normal file
3
requirements.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
PIL==1.1.7
|
||||||
|
mock==1.0.1
|
||||||
|
nose==1.3.0
|
||||||
10
sstv.py
10
sstv.py
|
|
@ -26,6 +26,7 @@ class SSTV(object):
|
||||||
|
|
||||||
BITS_TO_STRUCT = {8: 'b', 16: 'h'}
|
BITS_TO_STRUCT = {8: 'b', 16: 'h'}
|
||||||
def write_wav(self, filename):
|
def write_wav(self, filename):
|
||||||
|
"""writes the whole image to a Microsoft WAV file"""
|
||||||
fmt = '<' + self.BITS_TO_STRUCT[self.bits]
|
fmt = '<' + self.BITS_TO_STRUCT[self.bits]
|
||||||
data = ''.join(struct.pack(fmt, b) for b in self.gen_samples())
|
data = ''.join(struct.pack(fmt, b) for b in self.gen_samples())
|
||||||
with closing(wave.open(filename, 'wb')) as wav:
|
with closing(wave.open(filename, 'wb')) as wav:
|
||||||
|
|
@ -35,7 +36,8 @@ class SSTV(object):
|
||||||
wav.writeframes(data)
|
wav.writeframes(data)
|
||||||
|
|
||||||
def gen_samples(self):
|
def gen_samples(self):
|
||||||
"""generates bits from gen_values"""
|
"""generates discrete samples from gen_values(), performing quantization according to the bits per sample value given during construction
|
||||||
|
"""
|
||||||
max_value = 2 ** self.bits
|
max_value = 2 ** self.bits
|
||||||
alias = 1 / max_value
|
alias = 1 / max_value
|
||||||
amp = max_value / 2
|
amp = max_value / 2
|
||||||
|
|
@ -46,7 +48,8 @@ class SSTV(object):
|
||||||
yield max(min(highest, sample), lowest)
|
yield max(min(highest, sample), lowest)
|
||||||
|
|
||||||
def gen_values(self):
|
def gen_values(self):
|
||||||
"""generates -1 .. +1 values from freq_bits"""
|
"""generates samples between -1 and +1 from gen_freq_bits(), performing sampling according to the samples per second value given during construction
|
||||||
|
"""
|
||||||
spms = self.samples_per_sec / 1000
|
spms = self.samples_per_sec / 1000
|
||||||
param = 0
|
param = 0
|
||||||
samples = 0
|
samples = 0
|
||||||
|
|
@ -61,7 +64,8 @@ class SSTV(object):
|
||||||
samples -= tx
|
samples -= tx
|
||||||
|
|
||||||
def gen_freq_bits(self):
|
def gen_freq_bits(self):
|
||||||
"""generates (freq, msec) tuples from image"""
|
"""generates tuples (freq, msec) that describe a sine wave segment with frequency in Hz and duration in ms
|
||||||
|
"""
|
||||||
yield FREQ_VIS_START, MSEC_VIS_START
|
yield FREQ_VIS_START, MSEC_VIS_START
|
||||||
yield FREQ_SYNC, MSEC_VIS_SYNC
|
yield FREQ_SYNC, MSEC_VIS_SYNC
|
||||||
yield FREQ_VIS_START, MSEC_VIS_START
|
yield FREQ_VIS_START, MSEC_VIS_START
|
||||||
|
|
|
||||||
BIN
tests/assets/320x256.png
Normal file
BIN
tests/assets/320x256.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 167 KiB |
2894
tests/assets/MartinM1_encode_line_lena1.p
Normal file
2894
tests/assets/MartinM1_encode_line_lena1.p
Normal file
File diff suppressed because it is too large
Load diff
2894
tests/assets/MartinM1_encode_line_lena10.p
Normal file
2894
tests/assets/MartinM1_encode_line_lena10.p
Normal file
File diff suppressed because it is too large
Load diff
2894
tests/assets/MartinM1_encode_line_lena100.p
Normal file
2894
tests/assets/MartinM1_encode_line_lena100.p
Normal file
File diff suppressed because it is too large
Load diff
3002
tests/assets/MartinM1_freq_bits.p
Normal file
3002
tests/assets/MartinM1_freq_bits.p
Normal file
File diff suppressed because it is too large
Load diff
3002
tests/assets/MartinM1_freq_bits_lena.p
Normal file
3002
tests/assets/MartinM1_freq_bits_lena.p
Normal file
File diff suppressed because it is too large
Load diff
1002
tests/assets/MartinM1_values.p
Normal file
1002
tests/assets/MartinM1_values.p
Normal file
File diff suppressed because it is too large
Load diff
1002
tests/assets/SSTV_gen_samples.p
Normal file
1002
tests/assets/SSTV_gen_samples.p
Normal file
File diff suppressed because it is too large
Load diff
1002
tests/assets/SSTV_gen_values.p
Normal file
1002
tests/assets/SSTV_gen_values.p
Normal file
File diff suppressed because it is too large
Load diff
45
tests/test_color.py
Normal file
45
tests/test_color.py
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
import unittest
|
||||||
|
from itertools import islice
|
||||||
|
import pickle
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
import color
|
||||||
|
|
||||||
|
|
||||||
|
class TestMartinM1(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.image = Image.new('RGB', (320, 256))
|
||||||
|
self.s = color.MartinM1(self.image, 48000, 16)
|
||||||
|
lena = Image.open('tests/assets/320x256.png')
|
||||||
|
self.lena = color.MartinM1(lena, 48000, 16)
|
||||||
|
|
||||||
|
def test_gen_freq_bits(self):
|
||||||
|
expected = pickle.load(open("tests/assets/MartinM1_freq_bits.p"))
|
||||||
|
actual = list(islice(self.s.gen_freq_bits(), 0, 1000))
|
||||||
|
self.assertEqual(expected, actual)
|
||||||
|
|
||||||
|
def test_gen_freq_bits_lena(self):
|
||||||
|
expected = pickle.load(open("tests/assets/MartinM1_freq_bits_lena.p"))
|
||||||
|
actual = list(islice(self.lena.gen_freq_bits(), 0, 1000))
|
||||||
|
self.assertEqual(expected, actual)
|
||||||
|
|
||||||
|
def test_encode_line(self):
|
||||||
|
zeroth = list(self.s.encode_line(0))
|
||||||
|
first = list(self.s.encode_line(1))
|
||||||
|
tenth = list(self.s.encode_line(10))
|
||||||
|
eleventh = list(self.s.encode_line(11))
|
||||||
|
|
||||||
|
self.assertEqual(zeroth, first)
|
||||||
|
self.assertEqual(tenth, eleventh)
|
||||||
|
self.assertEqual(zeroth, eleventh)
|
||||||
|
|
||||||
|
def test_encode_line_lena(self):
|
||||||
|
self.maxDiff = None
|
||||||
|
line_numbers = [1, 10, 100]
|
||||||
|
for line in line_numbers:
|
||||||
|
file = open("tests/assets/MartinM1_encode_line_lena%d.p" % line)
|
||||||
|
expected = pickle.load(file)
|
||||||
|
actual = list(self.lena.encode_line(line))
|
||||||
|
self.assertEqual(expected, actual)
|
||||||
78
tests/test_sstv.py
Normal file
78
tests/test_sstv.py
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
import unittest
|
||||||
|
from itertools import islice
|
||||||
|
import pickle
|
||||||
|
import mock
|
||||||
|
from mock import MagicMock
|
||||||
|
from StringIO import StringIO
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
import sstv
|
||||||
|
from sstv import SSTV
|
||||||
|
|
||||||
|
|
||||||
|
class TestSSTV(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.s = SSTV(False, 48000, 16)
|
||||||
|
self.s.VIS_CODE = 0x00
|
||||||
|
self.s.SYNC = 7
|
||||||
|
|
||||||
|
def test_horizontal_sync(self):
|
||||||
|
horizontal_sync = self.s.horizontal_sync()
|
||||||
|
expected = (1200, self.s.SYNC)
|
||||||
|
actual = horizontal_sync.next()
|
||||||
|
self.assertEqual(expected, actual)
|
||||||
|
|
||||||
|
def test_gen_freq_bits(self):
|
||||||
|
gen_freq_bits = self.s.gen_freq_bits()
|
||||||
|
expected = [(1900, 300),
|
||||||
|
(1200, 10),
|
||||||
|
(1900, 300),
|
||||||
|
(1200, 30),
|
||||||
|
(1300, 30),
|
||||||
|
(1300, 30),
|
||||||
|
(1300, 30),
|
||||||
|
(1300, 30),
|
||||||
|
(1300, 30),
|
||||||
|
(1300, 30),
|
||||||
|
(1300, 30),
|
||||||
|
(1300, 30),
|
||||||
|
(1200, 30)]
|
||||||
|
actual = list(islice(gen_freq_bits, 0, 1000))
|
||||||
|
self.assertEqual(expected, actual)
|
||||||
|
|
||||||
|
# FIXME: Instead of using a test fixture, 'expected' should be synthesized?
|
||||||
|
def test_gen_values(self):
|
||||||
|
gen_values = self.s.gen_values()
|
||||||
|
expected = pickle.load(open("tests/assets/SSTV_gen_values.p"))
|
||||||
|
actual = list(islice(gen_values, 0, 1000))
|
||||||
|
self.assertEqual(expected, actual)
|
||||||
|
|
||||||
|
def test_gen_samples(self):
|
||||||
|
gen_values = self.s.gen_samples()
|
||||||
|
# gen_samples uses random to avoid quantization noise
|
||||||
|
# by using additive noise, so there's always a chance
|
||||||
|
# of running the code two consecutive times on the same machine
|
||||||
|
# and having different results.
|
||||||
|
# https://en.wikipedia.org/wiki/Quantization_%28signal_processing%29
|
||||||
|
sstv.random = MagicMock(return_value=0.4) # xkcd:221
|
||||||
|
expected = pickle.load(open("tests/assets/SSTV_gen_samples.p"))
|
||||||
|
actual = list(islice(gen_values, 0, 1000))
|
||||||
|
self.assertEqual(expected, actual)
|
||||||
|
|
||||||
|
def test_write_wav(self):
|
||||||
|
self.maxDiff = None
|
||||||
|
sio = StringIO()
|
||||||
|
sio.close = MagicMock() # ignore close() so we can .getvalue()
|
||||||
|
mock_open = MagicMock(return_value=sio)
|
||||||
|
with mock.patch('__builtin__.open', mock_open):
|
||||||
|
self.s.write_wav('unittest.wav')
|
||||||
|
expected = 'bf61c82e96aed1370d5c1753d87729db'
|
||||||
|
data = sio.getvalue()
|
||||||
|
actual = hashlib.md5(data).hexdigest()
|
||||||
|
self.assertEqual(expected, actual)
|
||||||
|
|
||||||
|
def test_init(self):
|
||||||
|
self.assertEqual(self.s.image, False)
|
||||||
|
self.assertEqual(self.s.samples_per_sec, 48000)
|
||||||
|
self.assertEqual(self.s.bits, 16)
|
||||||
Loading…
Reference in a new issue