Commit 7c206817 authored by blanch's avatar blanch
Browse files

splitted test_opengl with a model

parent 03023f1b
......@@ -7,10 +7,11 @@ import os
import time
import rasterio
from rasterio.enums import Resampling
import numpy as np
import scipy.ndimage as nd
if True:
if True and False:
import OpenGL
OpenGL.ERROR_CHECKING = False
OpenGL.ERROR_LOGGING = False
......@@ -20,18 +21,17 @@ from OpenGL.GL import *
from PyQt5.QtCore import (
Qt, QCoreApplication,
QRect,
QObject, pyqtSignal, pyqtSlot,
)
from PyQt5.QtGui import (
QSurfaceFormat,
QPainter, QBrush, QColor, QPen,
QPainter, QColor,
)
from PyQt5.QtWidgets import (
QApplication, QMainWindow, QWidget,
QPushButton, QOpenGLWidget,
QHBoxLayout,
QVBoxLayout,
)
from gl_utils import *
......@@ -45,63 +45,160 @@ DATA_SET = 'GDM_DTs_geo_20190517_20190926_8rlks_crop_cmp.tiff'
#DATA_PATH = "/Users/Shared/datasets/insarviz"
#DATA_SET = "data_cube_tibet_flatsim/CNES_DTs_geo_20141103_20201002_8rlks.tiff"
dataset = rasterio.open(os.path.join(DATA_PATH, DATA_SET))
print("opened", dataset)
for attr in dataset.profile:
print(attr, dataset.profile[attr])
def load(i=0):
# load image i
index = dataset.indexes[i]
t0 = time.time()
band = dataset.read(index)
t1 = time.time()
print('loaded band', i, 'in', t1-t0, 's')
return band, dataset.dtypes[i]
# gl ########################################################################
def load_texture(i=0):
band, dtype = load(i)
assert dtype == 'float32'
assert band.nbytes == dataset.width*dataset.height*4
# data
if 'nodata' in dataset.profile:
bg = (band == dataset.profile['nodata'])
else:
bg = np.zeros_like(band)
v_i, v_a = np.percentile(band[~bg], [0, 100])
v = (band-v_i)/(v_a-v_i)
w, h = band.shape
z = np.ones((w, h, 2), dtype='float32')
z[:,:,0] = v
z[:,:,1][bg] = 0.
# texture
glEnable(GL_TEXTURE_2D)
texture_id = glGenTextures(1)
glBindTexture(GL_TEXTURE_2D, texture_id)
glTexParameter(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
glTexParameter(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)
glTexImage2D(
GL_TEXTURE_2D,
0, GL_LUMINANCE_ALPHA,
dataset.width, dataset.height, 0,
GL_LUMINANCE_ALPHA,
GL_FLOAT,
z
)
glGenerateMipmap(GL_TEXTURE_2D)
glBindTexture(GL_TEXTURE_2D, 0)
glDisable(GL_TEXTURE_2D)
return (dataset.width, dataset.height), (v_i, v_a), texture_id
class Loader(QObject):
def open(self, filename=os.path.join(DATA_PATH, DATA_SET)):
self.dataset = rasterio.open(filename)
print("opened", self.dataset)
profile = self.dataset.profile
for attr in profile:
print(attr, profile[attr])
def __len__(self):
return len(self.dataset.indexes)
def load_band(self, i=0):
"""load image i"""
dataset = self.dataset
index = dataset.indexes[i]
t0 = time.time()
band = dataset.read(index,
out_shape = (dataset.height//1, dataset.width//1),
resampling=Resampling.nearest,
)
t1 = time.time()
print('loaded band', i, 'in', t1-t0, 's')
return band, dataset.profile.get('nodata', None), dataset.dtypes[i]
# Model #####################################################################
class MapModel(QObject):
texture_changed = pyqtSignal()
tex_id = 0
tex_width = 512
tex_height = 512
tex_vi = 0.
tex_va = 1.
bounds_changed = pyqtSignal()
cx = tex_width // 2
cy = tex_height // 2
z = 1.
def __init__(self, loader):
super().__init__()
self.loader = loader
self.textures = {}
def show_band(self, i):
try:
(
self.tex_id,
self.tex_width, self.tex_height,
self.tex_vi, self.tex_va,
) = self.textures[i]
except KeyError:
band, nd, dtype = self.loader.load_band(i)
assert dtype == 'float32'
# data
if nd is None:
bg = np.zeros_like(band)
else:
bg = (band == nd)
v_i, v_01, v_99, v_a = np.percentile(band[~bg], [0, 1, 99, 100])
v = (band-v_i)/(v_a-v_i)
h, w = band.shape
z = np.ones((h, w, 2), dtype='float32')
z[:,:,0] = v
z[:,:,1][bg] = 0.
# texture
glEnable(GL_TEXTURE_2D)
texture_id = glGenTextures(1)
glBindTexture(GL_TEXTURE_2D, texture_id)
glTexParameter(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
glTexParameter(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)
glTexImage2D(
GL_TEXTURE_2D,
0, GL_LUMINANCE_ALPHA,
w, h, 0,
GL_LUMINANCE_ALPHA,
GL_FLOAT,
z
)
glGenerateMipmap(GL_TEXTURE_2D)
glBindTexture(GL_TEXTURE_2D, 0)
glDisable(GL_TEXTURE_2D)
self.textures[i] = (
self.tex_id,
self.tex_width, self.tex_height,
self.tex_vi, self.tex_va,
) = (
texture_id,
w, h,
v_i, v_a,
)
self.texture_changed.emit()
if len(self.textures) == 1:
self.cx = self.tex_width // 2
self.cy = self.tex_height // 2
self.z = 1.
self.bounds_changed.emit()
def resized(self, width, height):
"""to be called when main map is resized"""
self.map_width = width
self.map_height = height
self.bounds_changed.emit()
# pan/zoom
def zoom(self, ds, x=None, y=None):
dz = np.exp(ds*.01)
self.z *= dz
if x != None:
w, h = self.map_width, self.map_height
self.cx += (x-w//2)/self.z * (dz-1.)
self.cy += (y-h//2)/self.z * (dz-1.)
self.bounds_changed.emit()
def pan(self, dx, dy):
self.cx -= dx/self.z
self.cy -= dy/self.z
self.bounds_changed.emit()
# tests
def test_open(self, *args):
# open action for testing
self.loader.open()
i = -1
def next_band(self, *args):
self.i = (self.i+1) % len(self.loader)
self.show_band(self.i)
def prev_band(self, *args):
self.i = (self.i-1) % len(self.loader)
self.show_band(self.i)
# opengl view ###############################################################
COLOR_SHADER = r"""
uniform sampler2D texture_2d;
......@@ -148,14 +245,52 @@ COLOR_SHADER = r"""
}
"""
GRAY_SHADER = """
uniform sampler2D texture_2d;
uniform float u_i;
uniform float u_a;
void main() {
// retreive original value, and color code
vec4 t = texture2D(texture_2d, gl_TexCoord[0].st);
float u = (t.x-u_i)/(u_a-u_i);
gl_FragColor = gl_Color * vec4(u, u, u, 1.);
}
"""
# opengl view ###############################################################
IDLE, DRAG, ZOOM = range(3)
class Map(QOpenGLWidget):
def __init__(self, model):
super().__init__()
self.model = model
self.model.bounds_changed.connect(self.update)
self.model.texture_changed.connect(self.texture_changed)
self.resized.connect(self.size_changed)
@pyqtSlot()
def texture_changed(self):
self.makeCurrent()
glUseProgram(self.program)
set_uniform(self.program, 'v_i', self.model.tex_vi)
set_uniform(self.program, 'v_a', self.model.tex_va)
# text coords in texture pixels
tw, th = self.model.tex_width, self.model.tex_height
glMatrixMode(GL_TEXTURE)
glLoadIdentity()
glScale(1./tw, 1./th, 1.)
self.doneCurrent()
self.update()
@pyqtSlot()
def size_changed(self):
self.model.resized(self.width(), self.height())
# opengl
def initializeGL(self):
......@@ -165,22 +300,9 @@ class Map(QOpenGLWidget):
)
tex_unit = 0
glActiveTexture(GL_TEXTURE0+tex_unit)
self.tex_size, (v_i, v_a), self.tex_id = load_texture()
set_uniform(self.program, 'texture_2d', tex_unit)
set_uniform(self.program, 'v_i', v_i)
set_uniform(self.program, 'v_a', v_a)
# text coords in texture pixels
w, h = self.tex_size
glMatrixMode(GL_TEXTURE)
glLoadIdentity()
glScale(1./w, 1./h, 1.)
self.cx, self.cy = w//2, h//2 # texture center
self.z = 1. # texture scale
glClearColor(.5, .5, .5, 1.)
glEnable(GL_TEXTURE_2D)
def resizeGL(self, width, height):
glViewport(0, 0, width, height)
......@@ -195,14 +317,18 @@ class Map(QOpenGLWidget):
def paintGL(self):
# band using OpenGL texturing
glClear(GL_COLOR_BUFFER_BIT)
glBindTexture(GL_TEXTURE_2D, self.tex_id)
glEnable(GL_TEXTURE_2D)
glBindTexture(GL_TEXTURE_2D, self.model.tex_id)
glUseProgram(self.program)
cx, cy = self.model.cx, self.model.cy
z = self.model.z
w, h = self.width(), self.height()
glMatrixMode(GL_TEXTURE)
glPushMatrix()
glTranslate(self.cx, self.cy, 0.)
glScale(1./self.z, 1./self.z, 1.)
glTranslate(cx, cy, 0.)
glScale(1./z, 1./z, 1.)
glTranslate(-w//2, -h//2, 0.)
glBegin(GL_TRIANGLE_STRIP)
......@@ -213,6 +339,7 @@ class Map(QOpenGLWidget):
glEnd()
glPopMatrix()
glUseProgram(0)
glDisable(GL_TEXTURE_2D)
# overlay using QPainter
painter = QPainter(self)
......@@ -221,11 +348,10 @@ class Map(QOpenGLWidget):
(0, 0, QColor('white')),
]:
painter.setPen(color)
painter.drawText(10+dx, 20+dy, 'z = %.02f' % (self.z,))
painter.drawText(10+dx, 40+dy, 'x = %.02f' % (self.cx,))
painter.drawText(10+dx, 60+dy, 'y = %.02f' % (self.cy,))
minimap_window.update()
painter.drawText(10+dx, 20+dy, 'z = %.02f' % (z,))
painter.drawText(10+dx, 40+dy, 'x = %.02f' % (cx,))
painter.drawText(10+dx, 60+dy, 'y = %.02f' % (cy,))
painter.drawText(10+dx, 80+dy, 'i = %i' % (self.model.i,))
# interaction
......@@ -248,11 +374,11 @@ class Map(QOpenGLWidget):
x1, y1 = e.x(), self.height()-e.y()
dx, dy = x1-x0, y1-y0
if self.interaction == DRAG:
self.pan(dx, dy)
self.model.pan(dx, dy)
elif self.interaction == ZOOM:
self.zoom(dx-dy, *self.p)
self.model.zoom(dx-dy, *self.p)
self.p0 = x1, y1
def mouseReleaseEvent(self, e):
if self.interaction == DRAG:
if e.button() == Qt.LeftButton:
......@@ -264,32 +390,9 @@ class Map(QOpenGLWidget):
def wheelEvent(self, e):
x, y = e.x(), self.height()-e.y()
ds = e.angleDelta().y()/8 # degrees
self.zoom(ds, x, y)
self.model.zoom(ds, x, y)
# pan/zoom
def zoom(self, ds, x=None, y=None):
dz = np.exp(ds*.01)
self.z *= dz
if x != None:
w, h = self.width(), self.height()
self.cx += (x-w//2)/self.z * (dz-1.)
self.cy += (y-h//2)/self.z * (dz-1.)
self.update()
def pan(self, dx, dy):
self.cx -= dx/self.z
self.cy -= dy/self.z
self.update()
# band handling
def next_band(*args):
print(args)
# palette handling
rainbow = False
......@@ -305,15 +408,41 @@ class Map(QOpenGLWidget):
class MiniMap(QOpenGLWidget):
def __init__(self, model):
super().__init__()
self.model = model
self.model.bounds_changed.connect(self.update)
self.model.texture_changed.connect(self.texture_changed)
@pyqtSlot()
def texture_changed(self):
self.makeCurrent()
self.resizeGL(self.width(), self.height())
# TODO
# values should come from texture distribution, and be controlled interactively
glUseProgram(self.program)
set_uniform(self.program, 'u_i', 0.)
set_uniform(self.program, 'u_a', 1.)
self.doneCurrent()
self.update()
# opengl
def initializeGL(self):
self.program = create_program(
create_shader(GL_FRAGMENT_SHADER, GRAY_SHADER),
)
tex_unit = 0
glActiveTexture(GL_TEXTURE0+tex_unit)
set_uniform(self.program, 'texture_2d', tex_unit)
glClearColor(.5, .5, .5, 1.)
def resizeGL(self, width, height):
glViewport(0, 0, width, height)
wr = width/height
tw, th = map_window.tex_size
tw, th = self.model.tex_width, self.model.tex_height
tr = tw/th
if tr > wr:
......@@ -336,8 +465,10 @@ class MiniMap(QOpenGLWidget):
def paintGL(self):
# band using OpenGL texturing
glClear(GL_COLOR_BUFFER_BIT)
glUseProgram(self.program)
glEnable(GL_TEXTURE_2D)
glBindTexture(GL_TEXTURE_2D, map_window.tex_id)
glBindTexture(GL_TEXTURE_2D, self.model.tex_id)
glBegin(GL_TRIANGLE_STRIP)
for x in [0, 1]:
......@@ -346,10 +477,11 @@ class MiniMap(QOpenGLWidget):
glVertex(x, y)
glEnd()
glDisable(GL_TEXTURE_2D)
glUseProgram(0)
x, y, z = map_window.cx, map_window.cy, map_window.z
tw, th = map_window.tex_size
ww, wh = map_window.width(), map_window.height()
x, y, z = self.model.cx, self.model.cy, self.model.z
tw, th = self.model.tex_width, self.model.tex_height
ww, wh = self.model.map_width, self.model.map_height
x1, x2 = x-ww/2./z, x+ww/2./z
y1, y2 = y-wh/2./z, y+wh/2./z
......@@ -362,6 +494,8 @@ class MiniMap(QOpenGLWidget):
glVertex(x2, y1)
glEnd()
glPopMatrix()
glBindTexture(GL_TEXTURE_2D, 0)
# interaction
......@@ -384,23 +518,23 @@ class MiniMap(QOpenGLWidget):
dx, dy = x1-x0, y1-y0
ww, wh = self.width(), self.height()
tw, th = map_window.tex_size
wr = ww/wh
tr = tw/th
if tr > wr:
dy *= tr/wr
else:
dx *= wr/tr
z = map_window.z
if self.interaction == DRAG:
ww, wh = self.width(), self.height()
tw, th = self.model.tex_width, self.model.tex_height
wr = ww/wh
tr = tw/th
if tr > wr:
dy *= tr/wr
else:
dx *= wr/tr
z = self.model.z
dx *= tw/ww*z
dy *= th/wh*z
dx *= tw/ww*z
dy *= th/wh*z
if self.interaction == DRAG:
map_window.pan(-dx, -dy)
self.model.pan(-dx, -dy)
elif self.interaction == ZOOM:
map_window.zoom(dx-dy)
self.model.zoom(dx-dy)
self.p0 = x1, y1
def mouseReleaseEvent(self, e):
......@@ -412,8 +546,9 @@ class MiniMap(QOpenGLWidget):
self.interaction = IDLE
def wheelEvent(self, e):
# TODO: should be centered on cursor
ds = e.angleDelta().y()/8 # degrees
map_window.zoom(ds)
self.model.zoom(ds)
......@@ -423,14 +558,22 @@ class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
layout = QHBoxLayout()
layout = QVBoxLayout()
load_button = QPushButton('open',
clicked=map_model.test_open)
layout.addWidget(load_button)
band_button = QPushButton('>',
clicked=map_model.next_band)
layout.addWidget(band_button)
next_button = QPushButton('>')
next_button.clicked.connect(map_window.next_band)
layout.addWidget(next_button)
band_button = QPushButton('<',
clicked=map_model.prev_band)
layout.addWidget(band_button)
rainbow_button = QPushButton('palette')
rainbow_button.clicked.connect(map_window.next_palette)
rainbow_button = QPushButton('palette',
clicked=map_window.next_palette)
layout.addWidget(rainbow_button)
widget = QWidget()
......@@ -438,21 +581,24 @@ class MainWindow(QMainWindow):
self.setCentralWidget(widget)
QCoreApplication.setAttribute(Qt.AA_ShareOpenGLContexts)
app = QApplication(sys.argv)
map_window = Map()
loader = Loader()
map_model = MapModel(loader)
map_window = Map(map_model)
map_window.setWindowTitle('Map')
map_window.setMouseTracking(True)
minimap_window = MiniMap()
minimap_window = MiniMap(map_model)
minimap_window.setWindowTitle('MiniMap')
main_window = MainWindow()
main_window.show()
map_window.show()
minimap_window.show()
main_window.show()
app.exec_()
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment