Immvision以为没有传入ndarray,而type() 显示它确实是一个ndarray
我正在尝试用Python的 imgui_bundle cartopy应用,尽管我在做一个 ndarray,我仍然收到一个错误,提示需要一个 numpy.ndarray,尽管当我 type() 时,它说它是一个 numpy.ndarray。单纯在只有cartopy的情况下做一个 show() 时,地图确实会显示。现在我想在 imgui 中实现并使其具有交互性(即让用户鼠标悬停以识别经纬度)。单独的 imgui 部分(不显示地图)也能工作,cartopy的编码(不带 gltexture 的部分,只是一个简单的 show())也能工作。现在当我尝试把两者结合起来时,就不行了:
import io
import os
import numpy as np
import matplotlib.pyplot as plt
import cartopy
import cartopy.crs as ccrs
import cartopy.feature as cfeature
from imgui_bundle import imgui, immvision, ImVec2, immapp
class MapApp:
def __init__(self):
# Base map bounding box
self.base_lat_min, self.base_lat_max = 3, 5
self.base_lon_min, self.base_lon_max = -10, -7
# Current zoom extents start as base extents
self.lat_min, self.lat_max = self.base_lat_min, self.base_lat_max
self.lon_min, self.lon_max = self.base_lon_min, self.base_lon_max
self.fig, self.ax = plt.subplots(figsize=(8, 6), subplot_kw={'projection': ccrs.PlateCarree()})
self.texture_id = None
self.needs_redraw = True
self.width, self.height = 800, 600 # Image display size
self.zoom_factor = 1.0 # 1.0 means no zoom
cartopy.config['pre_existing_data_dir'] = os.getcwd()
cartopy.config['data_dir'] = os.getcwd()
def update_map_texture(self):
self.ax.clear()
self.ax.set_extent([self.lon_min, self.lon_max, self.lat_min, self.lat_max], crs=ccrs.PlateCarree())
self.ax.coastlines('50m')
self.ax.add_feature(cfeature.BORDERS, linestyle=':')
self.ax.add_feature(cfeature.LAND, facecolor='lightgray')
self.ax.add_feature(cfeature.OCEAN, facecolor='lightblue')
canvas = self.fig.canvas
canvas.draw()
w, h = canvas.get_width_height()
print(f"w: {w}, h: {h}")
buf = canvas.tostring_argb()
img_array = np.frombuffer(buf, dtype=np.uint8).reshape(h, w, 4)
img_array = img_array[:, :, [1, 2, 3, 0]]
print(F"img_array.shape({img_array.size}): {img_array.shape} type: {type(img_array)}")
if self.texture_id is None:
self.texture_id = immvision.GlTexture(img_array)
print(f"texture_id: {self.texture_id}")
else:
immvision.GlTexture.update_from_image(self.texture_id, img_array, False)
self.needs_redraw = False
def gui(self):
imgui.begin("Interactive Map with Zoom")
if self.needs_redraw or self.texture_id is None:
self.update_map_texture()
imgui.image(self.texture_id, ImVec2(self.width, self.height))
# Mouse position and coordinate display
mouse_pos = imgui.get_mouse_pos()
window_pos = imgui.get_window_position()
cursor_pos = imgui.get_cursor_pos()
image_pos = (window_pos.x + cursor_pos.x, window_pos.y + cursor_pos.y)
rel_x = mouse_pos.x - image_pos[0]
rel_y = mouse_pos.y - image_pos[1]
if 0 <= rel_x < self.width and 0 <= rel_y < self.height:
lon = self.lon_min + (rel_x / self.width) * (self.lon_max - self.lon_min)
lat = self.lat_max - (rel_y / self.height) * (self.lat_max - self.lat_min)
imgui.text(f"Mouse Lon: {lon:.4f} Lat: {lat:.4f}")
imgui.end()
if __name__ == "__main__":
app = MapApp()
immapp.run(gui_function=app.gui, window_title="imgui Cartopy Map with Zoom")
我得到的错误是:
self.texture_id = immvision.GlTexture(img_array)
~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^
TypeError: __init__(): incompatible function arguments. The following argument types are supported:
1. __init__(self) -> None
2. __init__(self, image: numpy.ndarray, is_color_order_bgr: bool = False) -> None
3. __init__(self, other: imgui_bundle._imgui_bundle.immvision.GlTexture) -> None
Invoked with types: imgui_bundle._imgui_bundle.immvision.GlTexture, ndarray
尽管我的打印语句显示它是一个 numpy.ndarray:
w: 800, h: 600
img_array.shape(1920000): (600, 800, 4) type: <class 'numpy.ndarray'>
我到底少了什么?
解决方案
不知道为什么,但当我创建新数组时代码就能工作
img_array = np.zeros((self.height, self.width, 4), dtype=np.uint8)
看起来它的大小、形状、dtype都相同,但出于某种原因它就能工作。
我还发现把你的数组转换为 np.array() 时代码也能工作
img_array = np.frombuffer(buf, dtype=np.uint8).reshape(h, w, 4)
img_array = np.array(img_array)
我只能猜测原始的 img_array 会把数据保留为内存中不同位置的视图,但C/C++代码可能需要一个在连续内存中的对象。
差异显示为 img_array.flags。
顺便说一句:
代码里还有其他问题。
它需要 get_window_pos() 而不是 get_window_position()。 (小笔误)
另一个问题是 immvision.GlTexture(img_array) 给出对象 GlTexture,不是数字 texture ID,变量 self.texture_id 可能会让人误解,因为在某些地方它需要 self.texture_id.texture_id 才能得到真正的 texture ID。
在某个地方它还需要得到 texture ref 而不是 texture id
imgui.image(
imgui.ImTextureRef(self.texture_id.texture_id),
ImVec2(self.width, self.height),
)
修改后我得到这个:
完整可运行的代码:
import io
import os
import numpy as np
import matplotlib.pyplot as plt
import cartopy
import cartopy.crs as ccrs
import cartopy.feature as cfeature
from imgui_bundle import imgui, immvision, ImVec2, immapp
def info(arr):
print("--- arr ---")
print(f"size: {arr.size}")
print(f"shape: {arr.shape}")
print(f"type: {type(arr)}")
print(f"dtype: {arr.dtype}")
print(f"strides: {arr.strides}")
print(f"flags:\n{arr.flags}", end="")
print(f"base: {arr.base}")
print("---")
class MapApp:
def __init__(self):
# Base map bounding box
self.base_lat_min, self.base_lat_max = 3, 5
self.base_lon_min, self.base_lon_max = -10, -7
# Current zoom extents start as base extents
self.lat_min, self.lat_max = self.base_lat_min, self.base_lat_max
self.lon_min, self.lon_max = self.base_lon_min, self.base_lon_max
self.fig, self.ax = plt.subplots(
figsize=(8, 6), subplot_kw={"projection": ccrs.PlateCarree()}
)
self.texture = None
self.needs_redraw = True
self.width, self.height = 800, 600 # Image display size
self.zoom_factor = 1.0 # 1.0 means no zoom
cartopy.config["pre_existing_data_dir"] = os.getcwd()
cartopy.config["data_dir"] = os.getcwd()
def update_map_texture(self):
self.ax.clear()
self.ax.set_extent(
[self.lon_min, self.lon_max, self.lat_min, self.lat_max],
crs=ccrs.PlateCarree(),
)
self.ax.coastlines("50m")
self.ax.add_feature(cfeature.BORDERS, linestyle=":")
self.ax.add_feature(cfeature.LAND, facecolor="lightgray")
self.ax.add_feature(cfeature.OCEAN, facecolor="lightblue")
canvas = self.fig.canvas
canvas.draw()
w, h = canvas.get_width_height()
print(f"w: {w}, h: {h}")
buf = canvas.tostring_argb()
img_array = np.frombuffer(buf, dtype=np.uint8).reshape(h, w, 4)
# img_array = np.zeros((self.height, self.width, 4), dtype=np.uint8)
info(img_array)
img_array = np.array(img_array)
info(img_array)
if self.texture is None:
self.texture = immvision.GlTexture(img_array)
print(
f"texture: {self.texture} | texture.texture_id: {self.texture.texture_id}"
)
else:
immvision.GlTexture.update_from_image(self.texture, img_array, False)
self.needs_redraw = False
def gui(self):
imgui.begin("Interactive Map with Zoom")
if self.needs_redraw or self.texture is None:
self.update_map_texture()
imgui.image(
imgui.ImTextureRef(
self.texture.texture_id
), # needs `.texture_id` and `ImTextureRef()`
ImVec2(self.width, self.height),
)
# Mouse position and coordinate display
mouse_pos = imgui.get_mouse_pos()
window_pos = imgui.get_window_pos() # ERROR: position
cursor_pos = imgui.get_cursor_pos()
image_pos = (window_pos.x + cursor_pos.x, window_pos.y + cursor_pos.y)
rel_x = mouse_pos.x - image_pos[0]
rel_y = mouse_pos.y - image_pos[1]
if 0 <= rel_x < self.width and 0 <= rel_y < self.height:
lon = self.lon_min + (rel_x / self.width) * (self.lon_max - self.lon_min)
lat = self.lat_max - (rel_y / self.height) * (self.lat_max - self.lat_min)
imgui.text(f"Mouse Lon: {lon:.4f} Lat: {lat:.4f}")
imgui.end()
if __name__ == "__main__":
app = MapApp()
immapp.run(gui_function=app.gui, window_title="imgui Cartopy Map with Zoom")
关于 np.zeros() 的思路,我在演示 demos_python/demos_imgui_explorer/implot3d_demo.py 中看到的,我是在GitHub的仓库 imgui_bundle 找到的。还有更多演示。
