~krystianch/pgrid-web-nojs

e8bd7abc7ac57c3937e13bd46c69c2f55b20437b — Krystian Chachuła 7 months ago b6c3b7e master
Clean up and rename to Pgrid Web NoJS
23 files changed, 79 insertions(+), 373 deletions(-)

D .github/workflows/ci.yml
M .gitignore
D Dockerfile
D Dockerfile.sh
D Dokumentacja.docx
M LICENSE
M README.md
D app.py
D configuration.py
D data/.DS_Store
D data/.placeholder
D managers/ImageProcess.py
A pgridweb/__init__.py
R managers/Equirec2Perspec.py => pgridweb/equirec2perspec.py
R generate_map.py => pgridweb/map.py
R wwwroot/compass.png => pgridweb/static/compass.png
R wwwroot/styles.css => pgridweb/static/styles.css
R templates/base.html => pgridweb/templates/base.html
R templates/index.html => pgridweb/templates/index.html
D requirements.txt
A setup.py
D test_equirec2perspec.py
D waitress_server.py
D .github/workflows/ci.yml => .github/workflows/ci.yml +0 -30
@@ 1,30 0,0 @@
name: Deploy project

on:
  push:
    branches: [ "main" ]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      -
        name: Set up QEMU
        uses: docker/setup-qemu-action@v2
      -
        name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v2
      -
        name: Login to Docker Hub
        uses: docker/login-action@v2
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}
      -
        name: Build and push
        uses: docker/build-push-action@v4
        with:
          platforms: linux/amd64,linux/arm64/v8
          image: clovers1254/360-webview
          push: true
          tags: clovers1254/360-webview:latest

M .gitignore => .gitignore +2 -162
@@ 1,163 1,3 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
.pybuilder/
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
#   For a library or package, you might want to ignore these files since the code is
#   intended to run in multiple environments; otherwise, check them in:
# .python-version

# pipenv
#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
#   However, in case of collaboration, if having platform-specific dependencies or dependencies
#   having no cross-platform support, pipenv may install dependencies that don't work, or not
#   install all needed dependencies.
#Pipfile.lock

# poetry
#   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
#   This is especially recommended for binary packages to ensure reproducibility, and is more
#   commonly ignored for libraries.
#   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock

# pdm
#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
#   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
#   in version control.
#   https://pdm.fming.dev/#use-with-ide
.pdm.toml

# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# pytype static type analyzer
.pytype/

# Cython debug symbols
cython_debug/

# PyCharm
#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can
#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
#  and can be added to the global gitignore or merged into this file.  For a more nuclear
#  option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

/data/img
/data/points.json
\ No newline at end of file
data/
**/__pycache__/

D Dockerfile => Dockerfile +0 -13
@@ 1,13 0,0 @@
# syntax=docker/dockerfile:1

FROM python:3.8-slim-buster
RUN apt-get update && apt-get install ffmpeg libsm6 libxext6  -y
WORKDIR /python-docker

COPY requirements.txt requirements.txt
RUN pip3 install -r requirements.txt
RUN pip3 install waitress

COPY . .

CMD [ "python3", "waitress_server.py" ]
\ No newline at end of file

D Dockerfile.sh => Dockerfile.sh +0 -13
@@ 1,13 0,0 @@
#!/bin/sh

docker stop 360-webview && docker rm 360-webview
docker build . -t 360-webview
docker run -d -it --name 360-webview --restart always -p 9001:9001 360-webview

# mkdir 360-webview
# cd 360-webview
# wget https://files.krystianch.com/pgrid-feit-012-v1.tar.gz
# tar -xvf pgrid-feit-012-v1.tar.gz
# rm pgrid-feit-012-v1.tar.gz
# docker stop 360-webview && docker rm 360-webview
# docker run -d -it --name 360-webview --restart always -p 9001:9001 -v $(pwd)/img:/python-docker/data/img clovers1254/360-webview
\ No newline at end of file

D Dokumentacja.docx => Dokumentacja.docx +0 -0
M LICENSE => LICENSE +1 -0
@@ 1,6 1,7 @@
MIT License

Copyright (c) 2023 Minh Nguyen Cong
Copyright (c) 2023 Krystian Chachuła

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

M README.md => README.md +6 -53
@@ 1,56 1,9 @@
# Streetview
# Pgrid Web NoJS

## Introduction
Spherical photo web viewer, without JavaScript.

### Purpose of the project
The aim of the project is to create free and open-source software that allows viewing a set of spherical photos in a web browser, including devices with low computing power, mobile devices, devices with limited Internet access and browsers without JavaScript support.
This is a fork of a MIT-licensed course project [360-webview-nojs][1] by three
students from Warsaw University of Technology: Spyro313 on GitHub,
Minh Nguyen Cong <mcong@box.com> and AndriiDemk on GitHub.

### Initial vision of the project
As the project is not to use javascript, the following buttons will be used for "rotating": up, down, right, left. Clicking the button will render the image slightly rotated in the desired direction.

### The nature of the problem
The problem of this project is to convert the input photo (equidistant projection) to the normal field of view (Both photos are in jpg format).

## Installation guide
Instructions for the administrator how to install the project:
1. Clone the repository from https://github.com/congminh1254/360-webview-nojs to your local machine.
2. Download and unzip the 360 degree photo dataset on your host computer.
3. Open a terminal and navigate to the root directory of the cloned repository.
4. To clear the previous Docker container, run the following command:
```
docker stop 360-webview && docker rm 360-webview
```
5. To compile the current version of the project into a Docker image, run the following command:
```
docker build . -t 360-webview
```
6. To deploy a Docker container with the dataset directory mounted in /python-docker/data, run the following command:
```
docker run -d -it --name 360-webview --restart always --mount type=bind,source=$HOME/data_folder,target=/python-docker/data -p 9001:9001 360-webview
```
This command will start a Docker container named `360-webview` which will automatically restart after rebooting the system (`--restart always`). It will also mount the dataset directory located in `$HOME/data_folder` in the `/python-docker/data` directory of the container where the application will look for 360 degree photos.

Finally, it will map port 9001 of the container to port 9001 of the host (`-p 9001:9001`) where the application will be accessible from the web browser.

7. Optional: To configure Nginx to proxy traffic from the Internet to port 9001, follow these steps:
     - Install Nginx if it is not already installed on your system.
     - Open the Nginx configuration file, usually located in `/etc/nginx/nginx.conf`, with a text editor.
     - Add the following code block in the `http` block:
       ```
        server {
          listen 80;
          server_name example.com;
          location / {
              proxy_pass http://localhost:9001;
              proxy_set_header Host $host;
              proxy_set_header X-Real-IP $remote_addr;
              proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
          }
        }
       ```
     - Replace `example.com` with your domain name or IP address.
     - Save and close the file, then restart Nginx with the following command:
       ```
       sudo systemctl restart nginx
       ```
     - Now you should be able to access the app from your web browser by going to "http://example.com" or your IP address.
[1]: https://github.com/congminh1254/360-webview-nojs

D app.py => app.py +0 -45
@@ 1,45 0,0 @@
from flask import Flask, render_template, request
from configuration import get_configuration
from generate_map import generate_map
from managers.ImageProcess import ImageProcess
import json
import os.path as path
import os
import random

app = Flask(__name__, static_folder='wwwroot',
            static_url_path='/static', template_folder='templates')
app.config['FLASK_APP'] = get_configuration('flask_app')
app.config['FLASK_ENV'] = get_configuration('flask_env')

img_folder = 'data/img'
if (path.exists(img_folder) == False):
    raise Exception('No image folder found')

map_file = 'data/points.json'
if (path.exists(map_file) == False):
    generate_map('data')

@app.route('/')
def index():
    img = request.args.get('img')
    if img == None:
        img = random.choice(os.listdir(img_folder))
    img.replace('/', '')
    print(img)
    x = int(request.args.get('x', '180'))
    y = int(request.args.get('y', '0'))
    zoom = int(request.args.get('zoom', '120'))
    processor = ImageProcess(f'{img_folder}/'+img)
    image = processor.get_image(zoom, x, y, 720, 1080)
    image = "data:image/jpeg;base64, "+image
    maps = json.load(open(map_file))
    for i in range(len(maps)):
        for j in range(len(maps[i])):
            if maps[i][j] != None:
                maps[i][j][0] = maps[i][j][0].replace('img/', '')
    return render_template('index.html', image=image, zoom=zoom, x=x, y=y, step=10, maps=maps, img=img)


if __name__ == '__main__':
    app.run(host=get_configuration('host'), port=get_configuration('port'))

D configuration.py => configuration.py +0 -11
@@ 1,11 0,0 @@
import os

default_config = {
    'flask_app': '360 Webview',
    'flask_env': 'development',
    'host': 'localhost',
    'port': 5432,
}

def get_configuration(name):
    return os.environ.get(name, default_config[name])

D data/.DS_Store => data/.DS_Store +0 -0
D data/.placeholder => data/.placeholder +0 -0
D managers/ImageProcess.py => managers/ImageProcess.py +0 -15
@@ 1,15 0,0 @@
import cv2 
import managers.Equirec2Perspec as E2P
import base64

class ImageProcess():
    def __init__(self, file_path) -> None:
        self.equirec = E2P.Equirectangular(file_path)
    
    def get_image(self, FOV, theta, phi, height, width):
        image = self.equirec.GetPerspective(FOV, theta, phi, height, width)
        retval, buffer = cv2.imencode('.jpg', image)
        jpg_as_text = base64.b64encode(buffer).decode("utf-8") 
        return jpg_as_text



A pgridweb/__init__.py => pgridweb/__init__.py +51 -0
@@ 0,0 1,51 @@
import base64
import json
import os
import pathlib
import random

import cv2
from flask import Flask, current_app, render_template, request

from .equirec2perspec import Equirectangular
from .map import generate_map

def index():
    img_dir = current_app.config["IMG_DIR"]

    img = request.args.get("img")
    img_path = img_dir / img if img else random.choice(list(img_dir.iterdir()))

    x = int(request.args.get("x", "180"))
    y = int(request.args.get("y", "0"))
    zoom = int(request.args.get("zoom", "120"))

    image = Equirectangular(str(img_path)).GetPerspective(zoom, x, y, 720, 1080)
    ret, buffer = cv2.imencode(".jpg", image)
    b64 = base64.b64encode(buffer).decode()
    assert ret, "Encoding failed"
    image = "data:image/jpeg;base64, " + b64

    # FIXME
    maps = json.load(open(current_app.config["MAP_FILE"]))
    for i in range(len(maps)):
        for j in range(len(maps[i])):
            if maps[i][j] is not None:
                maps[i][j][0] = maps[i][j][0].replace("img/", "")

    return render_template("index.html", image=image, zoom=zoom, x=x, y=y,
        step=10, maps=maps, img=img_path.name)

def create_app():
    app = Flask(__name__)

    app.config["IMG_DIR"] = pathlib.Path("data/img")
    if not os.path.exists(app.config["IMG_DIR"]):
        raise Exception("No image folder found")
    app.config["MAP_FILE"] = "data/points.json"
    if not os.path.exists(app.config["MAP_FILE"]):
        generate_map("data")

    app.add_url_rule("/", view_func=index)

    return app

R managers/Equirec2Perspec.py => pgridweb/equirec2perspec.py +2 -0
@@ 8,9 8,11 @@

import os
import sys

import cv2
import numpy as np


def xyz2lonlat(xyz):
    atan2 = np.arctan2
    asin = np.arcsin

R generate_map.py => pgridweb/map.py +2 -2
@@ 43,10 43,10 @@ def generate_map(folder):
        while len(points[x]) <= y:
            points[x].append(None)
        points[x][y] = coordinate
    
 
    min_x = 100000
    min_y = 100000
    max_x = 0
    max_y = 0

    json.dump(points, open(f'{folder}/points.json', 'w'))
\ No newline at end of file
    json.dump(points, open(f'{folder}/points.json', 'w'))

R wwwroot/compass.png => pgridweb/static/compass.png +0 -0
R wwwroot/styles.css => pgridweb/static/styles.css +0 -0
R templates/base.html => pgridweb/templates/base.html +0 -0
R templates/index.html => pgridweb/templates/index.html +0 -0
D requirements.txt => requirements.txt +0 -3
@@ 1,3 0,0 @@
flask
opencv-python
pytest
\ No newline at end of file

A setup.py => setup.py +15 -0
@@ 0,0 1,15 @@
#!/usr/bin/env python3

from distutils.core import setup

setup(
    packages=["pgridweb"],
    package_data={"pgridweb": [
        "static/*",
        "templates/*",
    ]},
    requires=[
        "flask",
        "numpy",
    ],
)

D test_equirec2perspec.py => test_equirec2perspec.py +0 -22
@@ 1,22 0,0 @@
import pytest
import cv2
import numpy as np
from managers.Equirec2Perspec import xyz2lonlat, lonlat2XY, Equirectangular

def test_xyz2lonlat():
    xyz = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) 
    expected_output = np.array([[np.pi/2, 0], [0, np.pi/2], [0, 0]])

    result = xyz2lonlat(xyz)
    np.testing.assert_almost_equal(result, expected_output)


def test_lonlat2XY():
    lonlat = np.array([[np.pi/2, 0], [0, np.pi/2], [0, 0]]) 

    shape = (100, 200) 
    expected_output = np.array([[150, 49], [99, 99], [99, 50]])

    result = lonlat2XY(lonlat, shape)
    np.testing.assert_almost_equal(result, expected_output, 0)
    
\ No newline at end of file

D waitress_server.py => waitress_server.py +0 -4
@@ 1,4 0,0 @@
from waitress import serve
import app

serve(app.app, host='0.0.0.0', port=9001)
\ No newline at end of file