I have no idea what happened here...

This commit is contained in:
2021-06-30 11:07:28 -04:00
parent b01f236f7c
commit b44a63312e
716 changed files with 236008 additions and 32 deletions

View File

@@ -0,0 +1,3 @@
#!/usr/bin/env python
__all__ = ['overlay', 'pyaudio_sstv', 'repeater']

View File

@@ -0,0 +1,167 @@
#!/usr/bin/env python
class Image(object):
def __init__(self, content):
self.content = content
def load(self):
return self
def __getitem__(self, item):
if isinstance(item, tuple):
x, y = item
return Image('{0}[(ROW({1}) + COL({2})) * 3'.format(self.content, y, x))
elif isinstance(item, int):
return Image('{0} + RGB({1})]'.format(self.content, item))
else:
raise NotImplementedError()
def __rmul__(self, n):
return Image('({1} * {0})'.format(self.content, float(n)))
def __mul__(self, n):
return Image('({0} * {1})'.format(self.content, float(n)))
def __rtruediv__(self, n):
return Image('({1} / {0})'.format(self.content, n))
def __truediv__(self, n):
return Image('({0} / {1})'.format(self.content, n))
def __radd__(self, n):
return Image('({1} + {0})'.format(self.content, n))
def __add__(self, n):
return Image('({0} + {1})'.format(self.content, n))
def __str__(self):
return self.content
from pysstv.color import MartinM1, MartinM2, PasokonP3, PasokonP5, PasokonP7
import re
supported = [MartinM1, MartinM2, PasokonP3, PasokonP5, PasokonP7]
ROW_RE = re.compile(r'ROW\(\d+\)')
def main(sstv_class=None):
if sstv_class is None:
sstv_class = MartinM1
elif sstv_class not in supported:
raise NotImplementedError()
sstv = sstv_class(Image('img'), 44100, 16)
n = 0
yield '#define ROW(x) x'
yield '#define COL(x) x'
yield '#define RGB(x) x'
yield 'void convert(unsigned char *img, float *freqs, float *msecs, const int width) {\nint frq = 0;'
history = []
lut = {}
same_as = {}
for freq, msec in sstv.gen_freq_bits():
printed = 'freqs[frq] = {1}; msecs[frq++] = {2};'.format(n, freq, msec)
key = ROW_RE.sub('row', printed)
old = lut.get(key)
if old is not None:
same_as[n] = old
else:
lut[key] = n
history.append((printed, key))
n += 1
del lut
m_start, m_len = gen_matches(same_as, history, n)
for i in xrange(same_as[m_start]):
yield history[i][0]
yield 'for (int row = 0; row < width * {0}; row += width) {{'.format(sstv.HEIGHT)
for i in xrange(same_as[m_start], same_as[m_start] + m_len - 1):
yield ' ' + history[i][1]
yield '}'
yield '}}\n\n#define FREQ_COUNT {0}'.format(n)
def gen_matches(same_as, history, n):
cur_start = None
cur_len = None
cur_end = None
for i in xrange(n):
if cur_start is None:
tmp = same_as.get(i)
if tmp is not None:
cur_len = 1
cur_start = i
cur_end = tmp
else:
tmp = same_as.get(i)
if tmp is not None and history[tmp][1] == history[cur_end + 1][1] and cur_start > cur_end:
cur_len += 1
cur_end += 1
else:
if tmp is not None and history[tmp][1] == history[cur_end + 1][1]:
return cur_start, cur_len
tmp = same_as.get(i)
if tmp is None:
cur_start = None
else:
cur_len = 1
cur_start = i
cur_end = tmp
def test(img_file):
from subprocess import Popen, PIPE, check_output
from os import remove, path
from PIL import Image
from datetime import datetime
import struct
exe = './codegen-test-executable'
if not path.exists('stb_image.h'):
from urllib import urlretrieve
urlretrieve('https://raw.githubusercontent.com/nothings/stb/master/stb_image.h', 'stb_image.h')
try:
for sstv_class in supported:
print 'Testing', sstv_class
gcc = Popen(['gcc', '-xc', '-lm', '-o', exe, '-'], stdin=PIPE)
start = datetime.now()
with open(path.join(path.dirname(__file__), 'codeman.c')) as cm:
c_src = cm.read().replace('#include "codegen.c"', '\n'.join(main(sstv_class)))
gcc.communicate(c_src)
gen_elapsed = datetime.now() - start
print ' - gengcc took', gen_elapsed
start = datetime.now()
gen = check_output([exe, img_file])
native_elapsed = datetime.now() - start
print ' - native took', native_elapsed
img = Image.open(img_file)
sstv = sstv_class(img, 44100, 16)
start = datetime.now()
try:
for n, (freq, msec) in enumerate(sstv.gen_freq_bits()):
assert gen[n * 8:(n + 1) * 8] == struct.pack('ff', freq, msec)
except AssertionError:
mode_name = sstv_class.__name__
with file('/tmp/{0}-c.bin'.format(mode_name), 'wb') as f:
f.write(gen)
with file('/tmp/{0}-py.bin'.format(mode_name), 'wb') as f:
for n, (freq, msec) in enumerate(sstv.gen_freq_bits()):
f.write(struct.pack('ff', freq, msec))
with file('/tmp/{0}.c'.format(mode_name), 'w') as f:
f.write(c_src)
print (" ! Outputs are different, they've been saved to "
"/tmp/{0}-{{c,py}}.bin, along with the C source code "
"in /tmp/{0}.c").format(mode_name)
python_elapsed = datetime.now() - start
print ' - python took', python_elapsed
print ' - speedup:', python_elapsed.total_seconds() / native_elapsed.total_seconds()
print 'OK'
finally:
try:
remove(exe)
except OSError:
pass
if __name__ == '__main__':
from sys import argv
if len(argv) > 2 and argv[1] == 'test':
test(argv[2])
else:
print '\n'.join(main())

View File

@@ -0,0 +1,22 @@
#!/usr/bin/env python
"""
This example streams the raw floating point samples to stdout in 4-byte
single precision format, so that it can be processed outside PySSTV.
Usage example: get_floats.py | play -r 44100 -t f32 -c 1 --norm -
"""
from PIL import Image
from pysstv.grayscale import Robot8BW
import struct, sys
def main():
img = Image.open("160x120bw.png")
sstv = Robot8BW(img, 44100, 16)
sstv.vox_enabled = True
for value in sstv.gen_values():
sys.stdout.write(struct.pack('f', value))
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,23 @@
#!/usr/bin/env python
"""
This example streams the raw floating point (freq, msec) tuples to stdout
in 4-byte single precision format (8 bytes per tuple), so that it can be
processed outside PySSTV.
Usage example using unixsstv/gen_values:
get_freq_bits.py | gen_values 44100 | play -r 44100 -t f32 -c 1 --norm -
"""
from PIL import Image
from pysstv.color import MartinM1
import struct, sys
def main():
img = Image.open("320x256rgb.png")
sstv = MartinM1(img, 44100, 16)
for freq, msec in sstv.gen_freq_bits():
sys.stdout.write(struct.pack('ff', freq, msec))
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,253 @@
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
# copy to ~/.gimp-2.8/plug-ins/
# dependencies: GIMP 2.8, python-imaging-tk, python-pyaudio
from gimpfu import register, main, pdb, PF_BOOL, PF_STRING, PF_RADIO, CLIP_TO_IMAGE
from PIL import Image, ImageTk
from Tkinter import Tk, Canvas, Button, Checkbutton, IntVar, Frame, LEFT, NW
from pysstv import __main__ as pysstv_main
from pysstv.examples.pyaudio_sstv import PyAudioSSTV
from pysstv.sstv import SSTV
from itertools import repeat
from threading import Thread
from Queue import Queue, Empty
from time import sleep
import gimp, os
MODULE_MAP = pysstv_main.build_module_map()
class AudioThread(Thread):
def __init__(self, sstv, parent):
Thread.__init__(self)
self.pas = PyAudioSSTV(sstv)
self.parent = parent
def run(self):
self.pas.execute()
self.parent.audio_thread_ended()
def stop(self):
if self.pas is not None:
self.pas.sampler = []
self.pas = None
class Sine1750(SSTV):
encode_line = None
def gen_freq_bits(self):
return repeat((1750, 1000))
class Transmitter(object):
def __init__(self, sstv, root, progress, set_ptt_pin, ptt_state):
def encode_line_hooked(line):
progress.update_image(line)
return self.original_encode_line(line)
self.progress = progress
self.sstv = sstv
self.original_encode_line = sstv.encode_line
sstv.encode_line = encode_line_hooked
self.root = root
self.tx_enabled = IntVar()
self.audio_thread = None
self.stopping = False
self.set_ptt_pin = set_ptt_pin
self.ptt_state = ptt_state
def set_ptt(self, state):
if self.set_ptt_pin is None:
return
if not state:
sleep(0.2)
self.set_ptt_pin(state != self.ptt_state)
if state:
sleep(0.2)
def start_stop_tx(self):
if self.tx_enabled.get():
self.stopping = False
self.audio_thread = AudioThread(self.sstv, self)
self.set_ptt(True)
self.audio_thread.start()
else:
self.stop()
if self.progress is not None:
self.progress.update_image()
def stop(self):
if self.audio_thread is not None:
self.stopping = True
self.audio_thread.stop()
self.set_ptt(False)
def audio_thread_ended(self):
if not self.stopping:
self.set_ptt(False)
self.tx_enabled.set(0)
def close(self):
self.root.destroy()
class CanvasUpdater(Thread):
def __init__(self, progress):
Thread.__init__(self)
self.progress = progress
self.queue = Queue()
self.should_run = True
def stop(self):
self.should_run = False
def update_image(self, line=None):
self.queue.put(line)
def run(self):
while self.should_run:
try:
self.progress.update_image(self.queue.get(timeout=0.5))
except Empty:
pass
class ProgressCanvas(Canvas):
def __init__(self, master, image):
self.height_ratio = 1
width, height = image.size
pixels = image.load()
RED, GREEN, BLUE = range(3)
self.colors = ['#{0:02x}{1:02x}{2:02x}'.format(
contrast(sum(pixels[x, y][RED] for x in xrange(width)) / width),
contrast(sum(pixels[x, y][GREEN] for x in xrange(width)) / width),
contrast(sum(pixels[x, y][BLUE] for x in xrange(width)) / width))
for y in xrange(height)]
if height / float(width) > 1.5:
width *= 2
elif width < 200:
width *= 2
height *= 2
self.height_ratio = 2
if (width, height) != image.size:
image = image.resize((width, height))
Canvas.__init__(self, master, width=width, height=height)
self.tk_img = ImageTk.PhotoImage(image)
self.update_image()
self.pack()
def update_image(self, line=None):
image = self.tk_img
self.create_image(0, 0, anchor=NW, image=image)
if line is not None:
fill = self.colors[line]
line *= self.height_ratio
self.create_line(0, line, image.width(), line, fill=fill)
def contrast(value):
if 80 < value < 160:
return value + (80 if value < 120 else -80)
else:
return 255 - value
def transmit_current_image(image, drawable, mode, vox, fskid, ptt_port, ptt_pin, ptt_state):
sstv = MODULE_MAP[mode]
if ptt_port is not None:
from serial import Serial
set_ptt_pin = getattr(Serial(ptt_port), 'set' + ptt_pin)
set_ptt_pin(ptt_state)
else:
set_ptt_pin = None
pil_img = match_image_with_sstv_mode(image_gimp_to_pil(image), sstv)
root = Tk()
cu = CanvasUpdater(ProgressCanvas(root, pil_img))
cu.start()
tm = Transmitter(init_sstv(sstv, pil_img, vox, fskid), root, cu, set_ptt_pin, ptt_state)
tm1750 = Transmitter(Sine1750(None, 44100, 16), None, None, set_ptt_pin, ptt_state)
buttons = Frame(root)
for text, tram in (('TX', tm), ('1750 Hz', tm1750)):
Checkbutton(buttons, text=text, indicatoron=False, padx=5, pady=5,
variable=tram.tx_enabled, command=tram.start_stop_tx).pack(side=LEFT)
Button(buttons, text="Close", command=tm.close).pack(side=LEFT)
buttons.pack()
root.mainloop()
for obj in (tm, tm1750, cu):
obj.stop()
def image_gimp_to_pil(image):
try:
sandbox = image.duplicate()
for layer in sandbox.layers:
if not layer.visible:
sandbox.remove_layer(layer)
sandbox.merge_visible_layers(CLIP_TO_IMAGE)
layer = sandbox.layers[0]
if not layer.is_rgb:
pdb.gimp_image_convert_rgb(sandbox)
if layer.has_alpha:
pdb.gimp_layer_flatten(layer)
w, h = layer.width, layer.height
pixels = layer.get_pixel_rgn(0, 0, w, h)[:, :] # all pixels
return Image.frombuffer('RGB', (w, h), pixels, 'raw', 'RGB', 0, 0)
finally:
gimp.delete(sandbox)
def match_image_with_sstv_mode(image, mode):
mode_size = mode.WIDTH, mode.HEIGHT
if image.size != mode_size:
image = image.resize(mode_size, Image.ANTIALIAS)
if 'grayscale' in mode.__module__:
image = image.convert('LA').convert('RGB')
return image
def init_sstv(mode, image, vox, fskid):
s = mode(image, 44100, 16)
s.vox_enabled = vox
if fskid:
s.add_fskid_text(fskid)
return s
def get_serial_ports():
try:
if os.name == 'nt':
from serial.tools.list_ports_windows import comports
elif os.name == 'posix':
from serial.tools.list_ports_posix import comports
else:
raise ImportError("Sorry: no implementation for your"
"platform ('%s') available" % (os.name,))
except ImportError:
yield "(couldn't import PySerial)", None
else:
yield "(disabled)", None
for port, desc, _ in comports():
yield '{0} ({1})'.format(port, desc.strip()), port
register(
"pysstv_for_gimp",
"PySSTV for GIMP",
"Transmits the current image using PySSTV",
"Andras Veres-Szentkiralyi",
"Andras Veres-Szentkiralyi",
"November 2013",
"<Image>/PySSTV/Transmit...",
"*",
[
(PF_RADIO, "mode", "SSTV mode", "MartinM1",
tuple((n, n) for n in sorted(MODULE_MAP.iterkeys()))),
(PF_BOOL, "vox", "Include VOX tones", True),
(PF_STRING, "fskid", "FSK ID", ""),
(PF_RADIO, "ptt_port", "PTT port", None,
tuple(get_serial_ports())),
(PF_RADIO, "ptt_pin", "PTT pin", "RTS",
tuple((n, n) for n in ("RTS", "DTR"))),
(PF_BOOL, "ptt_state", "PTT inversion", False),
],
[],
transmit_current_image
)
main()

View File

@@ -0,0 +1,17 @@
#!/usr/bin/env python
"""
Demonstrates adding text overlay (callsign, RSV, etc.) using PIL
See example output received by slowrx at the following URL:
http://vsza.hu/c6fa52b2c7b20320bdab2da15877f0efbd466e67d37c8d124a557367de9380da.png
"""
from PIL import Image, ImageFont, ImageDraw
from pysstv.grayscale import Robot8BW
img = Image.open("160x120bw.png")
font = ImageFont.load_default()
draw = ImageDraw.Draw(img)
draw.text((0, 0), "HA5VSA", (255,255,255), font=font)
sstv = Robot8BW(img, 44100, 16)
sstv.write_wav("overlay.wav")

View File

@@ -0,0 +1,51 @@
#!/usr/bin/env python
"""
Demonstrates playing the generated samples directly using PyAudio
Tested on PyAudio 0.2.7 http://people.csail.mit.edu/hubert/pyaudio/
"""
from __future__ import division
from pysstv.sstv import SSTV
from time import sleep
from itertools import islice
import struct, pyaudio
class PyAudioSSTV(object):
def __init__(self, sstv):
self.pa = pyaudio.PyAudio()
self.sstv = sstv
self.fmt = '<' + SSTV.BITS_TO_STRUCT[sstv.bits]
def __del__(self):
self.pa.terminate()
def execute(self):
self.sampler = self.sstv.gen_samples()
stream = self.pa.open(
format=self.pa.get_format_from_width(self.sstv.bits // 8),
channels=1, rate=self.sstv.samples_per_sec, output=True,
stream_callback=self.callback)
stream.start_stream()
while stream.is_active():
sleep(0.5)
stream.stop_stream()
stream.close()
def callback(self, in_data, frame_count, time_info, status):
frames = ''.join(struct.pack(self.fmt, b)
for b in islice(self.sampler, frame_count))
return frames, pyaudio.paContinue
def main():
from PIL import Image
from pysstv.grayscale import Robot8BW
img = Image.open("160x120bw.png")
sstv = Robot8BW(img, 44100, 16)
sstv.vox_enabled = True
PyAudioSSTV(sstv).execute()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,73 @@
#!/usr/bin/env python
"""
Simple repeater that monitors a single directory using inotify, and if
an image appears, it tries to repeat it on using the PyAudio example,
trying to match the mode used for receiving it. It can be tested by
simply copying/linking images to the directory or suing an SSTV
receiver such as slowrx or QSSTV.
"""
from __future__ import print_function
from pyinotify import WatchManager, Notifier, ProcessEvent, IN_CREATE
from pyaudio_sstv import PyAudioSSTV
from pysstv.color import MartinM1, MartinM2, ScottieS1, ScottieS2
from pysstv.grayscale import Robot8BW, Robot24BW
from PIL import Image
from os import path
# matches the abbreviations used by slowrx and QSSTV
MODE_MAP = {
'M1': MartinM1,
'M2': MartinM2,
'S1': ScottieS1,
'S2': ScottieS2,
'R8BW': Robot8BW,
'R24BW': Robot24BW,
}
class EventHandler(ProcessEvent):
def process_IN_CREATE(self, event):
filename = event.pathname
print('Found image', filename)
mode = get_module_for_filename(filename)
img = Image.open(filename)
if mode is None:
mode = get_module_for_image(img)
if mode is None:
print('No suitable mode found to repeat', filename)
return
print('Repeating image using', mode.__name__)
sstv = mode(img, 44100, 16)
sstv.vox_enabled = True
PyAudioSSTV(sstv).execute()
def get_module_for_filename(filename):
basename, _ = path.splitext(path.basename(filename))
for mode, module in MODE_MAP.iteritems():
if mode in basename:
return module
def get_module_for_image(image):
size = image.size
for mode in MODE_MAP.itervalues():
if all(i >= m for i, m in zip(size, (mode.WIDTH, mode.HEIGHT))):
return mode
def main():
from sys import argv, stderr
try:
directory = argv[1]
except IndexError:
print("Usage: {0} <directory>".format(argv[0]), file=stderr)
else:
watch(directory)
def watch(directory):
wm = WatchManager()
notifier = Notifier(wm, EventHandler())
wm.add_watch(directory, IN_CREATE)
notifier.loop()
if __name__ == '__main__':
main()