Maybe you can find the answer by studying my maze solver code:
The maze is read from a bitmap file as grayscale values in a fixed grid, and interpolated to a finite element mesh.
import getfem as gf
import numpy as np
import PIL.Image,PIL.ImageOps
img = PIL.Image.open("maze.png")
data = np.array(img)
data[:,:,:][data[:,:,3]<255] = [255,255,255,255]
img = PIL.ImageOps.grayscale(PIL.Image.fromarray(data))
data = np.array(img)
nonemptyrows = np.where(np.any(data[:,:]!=255,axis=1))[0]
nonemptycols = np.where(np.any(data[:,:]!=255,axis=0))[0]
data = data[nonemptyrows[0]:nonemptyrows[-1],nonemptycols[0]:nonemptycols[-1]] // 255
NY,NX = data.shape # number of elements in horizontal and vertical direction
B_RG = 99 # bottom mesh region
T_RG = 98 # top mesh region
m = gf.Mesh("import", "structured",
f"GT='GT_QK(2,1)';ORG=[-0.5,-0.5];SIZES=[{NX},{NY}];NSUBDIV=[{NX//4},{NY//4}]")
m.set_region(B_RG, m.outer_faces_with_direction([0,-1], np.pi/10))
m.set_region(T_RG, m.outer_faces_with_direction([0,1], np.pi/10))
mfu = gf.MeshFem(m,2) # vector fem with 2 components
mfu.set_classical_fem(2) # 2nd order Lagrange fem (9-node quads or 6-node trias)
mfp = gf.MeshFem(m,1) # scalar fem
mfp.set_classical_fem(1) # 1st order Lagrange fem (4-node quads or 3-node trias)
mfrho = gf.MeshFem(m,1) # scalar fem
mfrho.set_classical_fem(0) # elementwise constant fem (discontinuous)
mim = gf.MeshIm(m, 5)
md = gf.Model("real")
md.add_fem_variable("u", mfu)
md.add_fem_variable("p", mfp)
md.add_fem_data("rho", mfrho)
X = md.interpolation("X(1)", mfrho).round().astype(int)
Y = md.interpolation("X(2)", mfrho).round().astype(int)
md.set_variable("rho", data[NY-Y-1,X])
md.add_linear_term(mim, "(1-0.99999999*rho)*1e6*u.Test_u+Grad(u):Grad(Test_u)"
"+Grad(p).Test_u+Div(u)*Test_p") # stokes equations
md.add_linear_term(mim, "(p-100)*Test_p", T_RG)
md.add_linear_term(mim, "(p-0)*Test_p", B_RG)
md.solve("lsolver", "mumps")
mfrho.export_to_vtu("maze.vtu", mfrho, md.variable("rho"), "rho",
mfu, md.variable("u"), "Velocity",
mfp, md.variable("p"), "Pressure")