Initial checkin

This commit is contained in:
Kumi 2020-10-07 15:25:07 +02:00
parent 50cb3f655e
commit ed97894891
18 changed files with 291 additions and 14 deletions

5
.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
*.pyc
__pycache__/
migrations/
*.swp
db.sqlite3

1
converter/__init__.py Normal file
View file

@ -0,0 +1 @@
from converter.nona import convert

94
converter/native.py Normal file
View file

@ -0,0 +1,94 @@
import sys
from PIL import Image
from math import pi,sin,cos,tan,atan2,hypot,floor
from numpy import clip
# get x,y,z coords from out image pixels coords
# i,j are pixel coords
# face is face number
# edge is edge length
def outImgToXYZ(i,j,face,edge):
a = 2.0*float(i)/edge
b = 2.0*float(j)/edge
if face==0: # back
(x,y,z) = (-1.0, 1.0-a, 3.0 - b)
elif face==1: # left
(x,y,z) = (a-3.0, -1.0, 3.0 - b)
elif face==2: # front
(x,y,z) = (1.0, a - 5.0, 3.0 - b)
elif face==3: # right
(x,y,z) = (7.0-a, 1.0, 3.0 - b)
elif face==4: # top
(x,y,z) = (b-1.0, a -5.0, 1.0)
elif face==5: # bottom
(x,y,z) = (5.0-b, a-5.0, -1.0)
return (x,y,z)
# convert using an inverse transformation
def convertBack(imgIn,imgOut):
inSize = imgIn.size
outSize = imgOut.size
inPix = imgIn.load()
outPix = imgOut.load()
edge = inSize[0]/4 # the length of each edge in pixels
for i in range(outSize[0]):
face = int(i/edge) # 0 - back, 1 - left 2 - front, 3 - right
if face==2:
rng = range(0,int(edge*3))
else:
rng = range(int(edge), int(edge) * 2)
for j in rng:
if j<edge:
face2 = 4 # top
elif j>=2*edge:
face2 = 5 # bottom
else:
face2 = face
if face2 != 5:
outPix[i,j] = (0,0,0)
continue
(x,y,z) = outImgToXYZ(i,j,face2,edge)
theta = atan2(y,x) # range -pi to pi
r = hypot(x,y)
phi = atan2(z,r) # range -pi/2 to pi/2
# source img coords
uf = ( 2.0*edge*(theta + pi)/pi )
vf = ( 2.0*edge * (pi/2 - phi)/pi)
# Use bilinear interpolation between the four surrounding pixels
ui = floor(uf) # coord of pixel to bottom left
vi = floor(vf)
u2 = ui+1 # coords of pixel to top right
v2 = vi+1
mu = uf-ui # fraction of way across pixel
nu = vf-vi
# Pixel values of four corners
# import sys
# print('inPix ->', inPix)
# print('ui ->', ui)
# print('inSize[0]', inSize[0])
# bar = clip(vi,0,inSize[1]-1)
# print('bar ->', bar, type(bar), int(bar))
# baz = ui % inSize[0]
# print('baz ->', baz, type(baz))
# foo = inPix[ui % inSize[0], bar]
# sys.exit(-1)
A = inPix[ui % inSize[0],int(clip(vi,0,inSize[1]-1))]
B = inPix[u2 % inSize[0],int(clip(vi,0,inSize[1]-1))]
C = inPix[ui % inSize[0],int(clip(v2,0,inSize[1]-1))]
D = inPix[u2 % inSize[0],int(clip(v2,0,inSize[1]-1))]
# interpolate
(r,g,b) = (
A[0]*(1-mu)*(1-nu) + B[0]*(mu)*(1-nu) + C[0]*(1-mu)*nu+D[0]*mu*nu,
A[1]*(1-mu)*(1-nu) + B[1]*(mu)*(1-nu) + C[1]*(1-mu)*nu+D[1]*mu*nu,
A[2]*(1-mu)*(1-nu) + B[2]*(mu)*(1-nu) + C[2]*(1-mu)*nu+D[2]*mu*nu )
outPix[i,j] = (int(round(r)),int(round(g)),int(round(b)))
def convert(image):
inSize = image.size
imgOut = Image.new("RGB",(inSize[0],int(inSize[0]*3/4)),"black")
convertBack(image,imgOut)
return imgOut

15
converter/nona.py Normal file
View file

@ -0,0 +1,15 @@
import tempfile
import subprocess
import PIL.Image
def convert(infile):
pto = tempfile.NamedTemporaryFile()
image = tempfile.NamedTemporaryFile(suffix="." + infile.split(".")[-1].split("/")[-1])
with open(infile, "rb") as inimage:
image.write(inimage.read())
erect = ["erect2cubic", f"--erect={image.name}", f"--ptofile={pto.name}", "--filespec=PNG_m"]
subprocess.run(erect)
tiles = tempfile.TemporaryDirectory()
nona = ["nona", pto.name, "-o", tiles.name + "/out"]
subprocess.run(nona)
return PIL.Image.open(tiles.name + "/out0005.png")

0
core/__init__.py Normal file
View file

3
core/admin.py Normal file
View file

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

5
core/apps.py Normal file
View file

@ -0,0 +1,5 @@
from django.apps import AppConfig
class CoreConfig(AppConfig):
name = 'core'

View file

@ -0,0 +1,14 @@
from django.core.management.base import BaseCommand, CommandError
from core.models import Series, Code
class Command(BaseCommand):
help = 'Add new image series'
def add_arguments(self, parser):
parser.add_argument('title', type=str)
parser.add_argument('--series', required=True, type=str)
def handle(self, *args, **options):
code = Code.objects.create(title=options["title"], series=Series.objects.get(id=options["series"]))
self.stdout.write(self.style.SUCCESS(f'Successfully created code "{ code.id }"'))

View file

@ -0,0 +1,14 @@
from django.core.management.base import BaseCommand, CommandError
from core.models import Series
class Command(BaseCommand):
help = 'Add new image series'
def add_arguments(self, parser):
parser.add_argument('title', type=str)
parser.add_argument('--description', required=False, type=str)
def handle(self, *args, **options):
series = Series.objects.create(title=options["title"], description=options["description"])
self.stdout.write(self.style.SUCCESS(f'Successfully created series "{ series.id }"'))

View file

@ -0,0 +1,28 @@
from django.core.management.base import BaseCommand, CommandError
from core.models import Series, Code
class Command(BaseCommand):
help = 'Get details for a code'
def add_arguments(self, parser):
parser.add_argument('code', type=str)
def handle(self, *args, **options):
splits = options["code"].split(":")
if (not splits[0] == "EXP") or len(splits) > 3:
self.stderr.write(self.style.WARNING(f'This is not a valid code.'))
return
if len(splits) == 2:
self.stderr.write(self.style.WARNING(f'This is not a complete code. It may be used literally.'))
return
series_id = options["code"].split(":")[1]
code_id = options["code"].split(":")[2]
series = Series.objects.get(id=series_id)
code = Code.objects.get(id=code_id, series=series)
self.stdout.write(self.style.SUCCESS(f'"{ code.title }" from series "{ series.title }" ({ series.description })'))

91
core/models.py Normal file
View file

@ -0,0 +1,91 @@
from django.db.models import Model, UUIDField, CharField, TextField, ForeignKey, CASCADE, PositiveIntegerField
import uuid
import string
import random
class Series(Model):
id = CharField(primary_key=True, max_length=10)
uuid = UUIDField(unique=True)
title = CharField(max_length=128, blank=True, null=True)
description = TextField(blank=True, null=True)
@classmethod
def generate_id(cls):
id = "".join(random.SystemRandom().choices(string.digits + string.ascii_letters, k=10))
try:
cls.objects.get(id=id)
except cls.DoesNotExist:
return id
return cls.generate_id()
@classmethod
def generate_uuid(cls):
id = uuid.uuid4()
try:
cls.objects.get(uuid=id)
except cls.DoesNotExist:
return id
return cls.generate_uuid()
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if not self.id:
self.id = self.generate_id()
if not self.uuid:
self.uuid = self.generate_uuid()
class Code(Model):
id = CharField(max_length=5)
uuid = UUIDField(primary_key=True)
title = CharField(max_length=128)
series = ForeignKey(Series, CASCADE)
order = PositiveIntegerField()
class Meta:
unique_together = [["id", "series"]]
@classmethod
def generate_id(cls):
id = "".join(random.SystemRandom().choices(string.digits + string.ascii_letters, k=5))
try:
cls.objects.get(id=id)
except cls.DoesNotExist:
return id
return cls.generate_id()
@classmethod
def generate_uuid(cls):
id = uuid.uuid4()
try:
cls.objects.get(uuid=id)
except cls.DoesNotExist:
return id
return cls.generate_uuid()
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if not self.id:
self.id = self.generate_id()
if not self.uuid:
self.uuid = self.generate_uuid()
if not self.order:
self.order = max([code.order for code in self.series.code_set.all()] + [0]) + 1
class Scan(Model):
uuid = UUIDField()
code = ForeignKey(Code, CASCADE)
@classmethod
def generate_uuid(cls):
uuid = uuid.uuid4(primary_key=True)
try:
cls.objects.get(uuid=id)
except cls.DoesNotExist:
return id
return cls.generate_uuid()
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if not self.uuid:
self.uuid = self.generate_uuid()

3
core/tests.py Normal file
View file

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

3
core/views/__init__.py Normal file
View file

@ -0,0 +1,3 @@
from django.views import View
from django.http import JsonResponse

10
customsettings.py Normal file
View file

@ -0,0 +1,10 @@
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'me98p&0-ijj-*vov@vm_&z&x#gr9uvvc9*y$n!!%=+javz^-#7'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []

View file

@ -11,23 +11,11 @@ https://docs.djangoproject.com/en/3.1/ref/settings/
"""
from pathlib import Path
from customsettings import *
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'me98p&0-ijj-*vov@vm_&z&x#gr9uvvc9*y$n!!%=+javz^-#7'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
@ -37,6 +25,7 @@ INSTALLED_APPS = [
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'core',
]
MIDDLEWARE = [

0
reader/__init__.py Normal file
View file

View file

@ -1,2 +1,4 @@
django
pyqrcode
pyqrcode
PIL
numpy

0
writer/__init__.py Normal file
View file