Merge pull request #20547 from rogday:gdb_pretty_printer
* add gdb rpetty printer for cv::Mat * address review commentspull/20569/head
parent
8dcec034ed
commit
a50dec88d5
7 changed files with 276 additions and 2 deletions
After Width: | Height: | Size: 35 KiB |
@ -0,0 +1,38 @@ |
||||
Using OpenCV with gdb-powered IDEs {#tutorial_linux_gdb_pretty_printer} |
||||
===================== |
||||
|
||||
@prev_tutorial{tutorial_linux_install} |
||||
@next_tutorial{tutorial_linux_gcc_cmake} |
||||
|
||||
| | | |
||||
| -: | :- | |
||||
| Original author | Egor Smirnov | |
||||
| Compatibility | OpenCV >= 4.0 | |
||||
|
||||
@tableofcontents |
||||
|
||||
# Capabilities {#tutorial_linux_gdb_pretty_printer_capabilities} |
||||
|
||||
This pretty-printer can show element type, `is_continuous`, `is_submatrix` flags and (possibly truncated) matrix. It is known to work in Clion, VS Code and gdb. |
||||
|
||||
 |
||||
|
||||
|
||||
# Installation {#tutorial_linux_gdb_pretty_printer_installation} |
||||
|
||||
Move into `opencv/samples/gdb/`. Place `mat_pretty_printer.py` in a convinient place, rename `gdbinit` to `.gdbinit` and move it into your home folder. Change 'source' line of `.gdbinit` to point to your `mat_pretty_printer.py` path. |
||||
|
||||
In order to check version of python bundled with your gdb, use the following commands from the gdb shell: |
||||
|
||||
python |
||||
import sys |
||||
print(sys.version_info) |
||||
end |
||||
|
||||
If the version of python 3 installed in your system doesn't match the version in gdb, create a new virtual environment with the exact same version, install `numpy` and change the path to python3 in `.gdbinit` accordingly. |
||||
|
||||
|
||||
# Usage {#tutorial_linux_gdb_pretty_printer_usage} |
||||
|
||||
The fields in a debugger prefixed with `view_` are pseudo-fields added for convinience, the rest are left as is. |
||||
If you feel that the number of elements in truncated view is too low, you can edit `mat_pretty_printer.py` - `np.set_printoptions` controlls everything matrix display-related. |
@ -0,0 +1,23 @@ |
||||
set auto-load local-gdbinit on |
||||
set print elements 0 |
||||
add-auto-load-safe-path / |
||||
|
||||
python |
||||
# Update GDB's Python paths with the `sys.path` values of the local |
||||
# Python installation, whether that is brew'ed Python, a virtualenv, |
||||
# or another system python. |
||||
|
||||
# Convert GDB to interpret in Python |
||||
|
||||
import os, subprocess, sys |
||||
|
||||
# Execute a Python using the user's shell and pull out the sys.path (for site-packages) |
||||
paths = subprocess.check_output('/usr/bin/python3 -c "import os,sys;print(os.linesep.join(sys.path).strip())"',shell=True).decode("utf-8").split() |
||||
|
||||
# Extend GDB's Python's search path |
||||
sys.path.extend(paths) |
||||
|
||||
end |
||||
|
||||
|
||||
source /your/path/to/mat_pretty_printer.py |
@ -0,0 +1,212 @@ |
||||
import gdb |
||||
import numpy as np |
||||
from enum import Enum |
||||
|
||||
np.set_printoptions(suppress=True) # prevent numpy exponential notation on print, default False |
||||
# np.set_printoptions(threshold=sys.maxsize) |
||||
|
||||
|
||||
def conv(obj, t): |
||||
return gdb.parse_and_eval(f'({t})({obj})') |
||||
|
||||
|
||||
def booli(obj): |
||||
return conv(str(obj).lower(), 'bool') |
||||
|
||||
|
||||
def stri(obj): |
||||
s = f'"{obj}"' |
||||
return conv(s.translate(s.maketrans('\n', ' ')), 'char*') |
||||
|
||||
|
||||
class MagicValues(Enum): |
||||
MAGIC_VAL = 0x42FF0000 |
||||
AUTO_STEP = 0 |
||||
CONTINUOUS_FLAG = 1 << 14 |
||||
SUBMATRIX_FLAG = 1 << 15 |
||||
|
||||
|
||||
class MagicMasks(Enum): |
||||
MAGIC_MASK = 0xFFFF0000 |
||||
TYPE_MASK = 0x00000FFF |
||||
DEPTH_MASK = 7 |
||||
|
||||
|
||||
class Depth(Enum): |
||||
CV_8U = 0 |
||||
CV_8S = 1 |
||||
CV_16U = 2 |
||||
CV_16S = 3 |
||||
CV_32S = 4 |
||||
CV_32F = 5 |
||||
CV_64F = 6 |
||||
CV_16F = 7 |
||||
|
||||
|
||||
def create_enum(n): |
||||
def make_type(depth, cn): |
||||
return depth.value + ((cn - 1) << 3) |
||||
defs = [(f'{depth.name}C{i}', make_type(depth, i)) for depth in Depth for i in range(1, n + 1)] |
||||
return Enum('Type', defs) |
||||
|
||||
|
||||
Type = create_enum(512) |
||||
|
||||
|
||||
class Flags: |
||||
def depth(self): |
||||
return Depth(self.flags & MagicMasks.DEPTH_MASK.value) |
||||
|
||||
def dtype(self): |
||||
depth = self.depth() |
||||
ret = None |
||||
|
||||
if depth == Depth.CV_8U: |
||||
ret = (np.uint8, 'uint8_t') |
||||
elif depth == Depth.CV_8S: |
||||
ret = (np.int8, 'int8_t') |
||||
elif depth == Depth.CV_16U: |
||||
ret = (np.uint16, 'uint16_t') |
||||
elif depth == Depth.CV_16S: |
||||
ret = (np.int16, 'int16_t') |
||||
elif depth == Depth.CV_32S: |
||||
ret = (np.int32, 'int32_t') |
||||
elif depth == Depth.CV_32F: |
||||
ret = (np.float32, 'float') |
||||
elif depth == Depth.CV_64F: |
||||
ret = (np.float64, 'double') |
||||
elif depth == Depth.CV_16F: |
||||
ret = (np.float16, 'float16') |
||||
|
||||
return ret |
||||
|
||||
def type(self): |
||||
return Type(self.flags & MagicMasks.TYPE_MASK.value) |
||||
|
||||
def channels(self): |
||||
return ((self.flags & (511 << 3)) >> 3) + 1 |
||||
|
||||
def is_continuous(self): |
||||
return (self.flags & MagicValues.CONTINUOUS_FLAG.value) != 0 |
||||
|
||||
def is_submatrix(self): |
||||
return (self.flags & MagicValues.SUBMATRIX_FLAG.value) != 0 |
||||
|
||||
def __init__(self, flags): |
||||
self.flags = flags |
||||
|
||||
def __iter__(self): |
||||
return iter({ |
||||
'type': stri(self.type().name), |
||||
'is_continuous': booli(self.is_continuous()), |
||||
'is_submatrix': booli(self.is_submatrix()) |
||||
}.items()) |
||||
|
||||
|
||||
class Size: |
||||
def __init__(self, ptr): |
||||
self.ptr = ptr |
||||
|
||||
def dims(self): |
||||
return int((self.ptr - 1).dereference()) |
||||
|
||||
def to_numpy(self): |
||||
return np.array([int(self.ptr[i]) for i in range(self.dims())], dtype=np.int64) |
||||
|
||||
def __iter__(self): |
||||
return iter({'size': stri(self.to_numpy())}.items()) |
||||
|
||||
|
||||
class Mat: |
||||
def __init__(self, m, size, flags): |
||||
(dtype, ctype) = flags.dtype() |
||||
elsize = np.dtype(dtype).itemsize |
||||
|
||||
ptr = m['data'] |
||||
dataptr = int(ptr) |
||||
length = (int(m['dataend']) - dataptr) // elsize |
||||
start = (int(m['datastart']) - dataptr) // elsize |
||||
|
||||
if length == 0: |
||||
self.mat = np.array([]) |
||||
self.view = self.mat |
||||
return |
||||
|
||||
if dtype != np.float16: |
||||
ctype = gdb.lookup_type(ctype) |
||||
ptr = ptr.cast(ctype.array(length - 1).pointer()).dereference() |
||||
self.mat = np.array([ptr[i] for i in range(length)], dtype=dtype) |
||||
else: |
||||
u16 = gdb.lookup_type('uint16_t') |
||||
ptr = ptr.cast(u16.array(length - 1).pointer()).dereference() |
||||
self.mat = np.array([ptr[i] for i in range(length)], dtype=np.uint16) |
||||
self.mat = self.mat.view(np.float16) |
||||
|
||||
steps = np.asarray([int(m['step']['p'][i]) for i in range(size.dims())], dtype=np.int64) |
||||
self.view = np.lib.stride_tricks.as_strided(self.mat[start:], shape=size.to_numpy(), strides=steps) |
||||
|
||||
def __iter__(self): |
||||
return iter({'data': stri(self.view)}.items()) |
||||
|
||||
|
||||
class MatPrinter: |
||||
"""Print a cv::Mat""" |
||||
|
||||
def __init__(self, mat): |
||||
self.mat = mat |
||||
|
||||
def views(self): |
||||
m = self.mat |
||||
|
||||
flags = Flags(int(m['flags'])) |
||||
size = Size(m['size']['p']) |
||||
data = Mat(m, size, flags) |
||||
|
||||
for x in [flags, size, data]: |
||||
for k, v in x: |
||||
yield 'view_' + k, v |
||||
|
||||
def real(self): |
||||
m = self.mat |
||||
|
||||
for field in m.type.fields(): |
||||
k = field.name |
||||
v = m[k] |
||||
yield k, v |
||||
|
||||
# TODO: add an enum in interface.h with all cv::Mat element types and use that instead |
||||
# yield 'test', gdb.parse_and_eval(f'(cv::MatTypes)0') |
||||
|
||||
def children(self): # TODO: hide real members under new child somehow |
||||
yield from self.views() |
||||
yield from self.real() |
||||
|
||||
|
||||
def get_type(val): |
||||
# Get the type. |
||||
vtype = val.type |
||||
|
||||
# If it points to a reference, get the reference. |
||||
if vtype.code == gdb.TYPE_CODE_REF: |
||||
vtype = vtype.target() |
||||
|
||||
# Get the unqualified type, stripped of typedefs. |
||||
vtype = vtype.unqualified().strip_typedefs() |
||||
|
||||
# Get the type name. |
||||
typename = vtype.tag |
||||
|
||||
return typename |
||||
|
||||
|
||||
def mat_printer(val): |
||||
typename = get_type(val) |
||||
|
||||
if typename is None: |
||||
return None |
||||
|
||||
if str(typename) == 'cv::Mat': |
||||
return MatPrinter(val) |
||||
|
||||
|
||||
gdb.pretty_printers.append(mat_printer) |
Loading…
Reference in new issue