Initial checkin
This commit is contained in:
parent
50cb3f655e
commit
ed97894891
18 changed files with 291 additions and 14 deletions
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
*.pyc
|
||||
__pycache__/
|
||||
migrations/
|
||||
*.swp
|
||||
db.sqlite3
|
1
converter/__init__.py
Normal file
1
converter/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
from converter.nona import convert
|
94
converter/native.py
Normal file
94
converter/native.py
Normal 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
15
converter/nona.py
Normal 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
0
core/__init__.py
Normal file
3
core/admin.py
Normal file
3
core/admin.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
5
core/apps.py
Normal file
5
core/apps.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class CoreConfig(AppConfig):
|
||||
name = 'core'
|
14
core/management/commands/addcode.py
Normal file
14
core/management/commands/addcode.py
Normal 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 }"'))
|
14
core/management/commands/addseries.py
Normal file
14
core/management/commands/addseries.py
Normal 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 }"'))
|
28
core/management/commands/getdetails.py
Normal file
28
core/management/commands/getdetails.py
Normal 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
91
core/models.py
Normal 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
3
core/tests.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
3
core/views/__init__.py
Normal file
3
core/views/__init__.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from django.views import View
|
||||
from django.http import JsonResponse
|
||||
|
10
customsettings.py
Normal file
10
customsettings.py
Normal 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 = []
|
|
@ -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
0
reader/__init__.py
Normal file
|
@ -1,2 +1,4 @@
|
|||
django
|
||||
pyqrcode
|
||||
pyqrcode
|
||||
PIL
|
||||
numpy
|
0
writer/__init__.py
Normal file
0
writer/__init__.py
Normal file
Loading…
Reference in a new issue