Table Detection and Navigation#
from pathlib import Path
from dessia_drawing.core import Drawing
from drawing_tools.config.default_language_configs import DEFAULT_ENGLISH_CONFIG, DEFAULT_FRENCH_CONFIG
from drawing_tools.featured_drawing import FeaturedDrawing
from drawing_tools.table.detection import SheetTableDetector
_DATA_DIR = Path("data/json")
drawing = Drawing.from_json(str(_DATA_DIR / "2902060102_--A_DEF01_LH SUPPORT ACCUMULATOR ASSEMBLY.json"))
featured_drawing = FeaturedDrawing(drawing, language_configs=[DEFAULT_FRENCH_CONFIG, DEFAULT_ENGLISH_CONFIG])
featured_sheet = featured_drawing.sheets[1]
detector = SheetTableDetector(sheet=featured_sheet.sheet)
tables_dict = detector.detect_all_tables()
table = next((t for tables in tables_dict.values() for t in tables), None)
featured_sheet.plot_data_tables().plot()
Drawing Tools provides table detection and extraction capabilities for technical drawings. This guide explains how to detect, analyze, and extract data from tables.
Overview#
Technical drawings often contain tables such as:
Title blocks with drawing metadata
Bills of Materials (BOM)
Revision tables
Custom data tables
Drawing Tools can automatically detect these tables from the raw line geometry and extract their structure and content.
The following visualization is produced by featured_sheet.plot_data_tables(), which
renders each reconstructed table in full — every cell gets its own color (texts in the
cell color, background in the same color lightened, showing which text was attached to
which cell), with a frame and a summary label in the table color; the title block is
labeled in magenta. Native Table annotations keep a soft colored rectangle + label.
This method is also available on FeaturedDrawing to visualize all sheets at once.
Source: 2902060102_–A_DEF01_LH SUPPORT ACCUMULATOR ASSEMBLY.json, Sheet 1
See also
For a complete working example, see scripts/scripts_tables.py.
For a detailed description of the detection pipeline internals, see Table Detection Pipeline.
Quick Start#
from dessia_drawing.core import Drawing
from drawing_tools.table.detection import SheetTableDetector
drawing = Drawing.from_json("path/to/drawing.json")
sheet = drawing.sheets[0]
detector = SheetTableDetector(sheet=sheet)
# Detect all tables across all views
tables_dict = detector.detect_all_tables()
# Both returned kinds expose nb_rows / nb_cols / cells / source
for view_idx, tables in tables_dict.items():
for table in tables:
print(f"View {view_idx}: {table.nb_rows}r x {table.nb_cols}c from {table.source}")
# Detect the title block specifically
title_block = detector.detect_title_block()
Note
SheetTableDetector accepts both dessia_drawing.Sheet and FeaturedSheet objects.
The Different Kinds of Tables#
A drawing carries tables in three different forms, and the detector returns all of
them side by side. Each detected table names its origin in its source attribute:
Kind |
Returned type |
|
Where it comes from |
|---|---|---|---|
Native table annotation |
|
|
The source file already describes the table (rows/columns declared by CATIA);
nothing is reconstructed. Collected across nesting levels, so tables inside a
|
Framed note |
|
|
A |
Edge-based table |
|
|
Reconstructed from the raw H/V line segments: the title block, revision and
NOTA blocks of the sheet frame ( |
Title block |
|
(edge-based) |
Not a fourth branch: the edge-based table of the background view closest to
the bottom-right corner, selected by |
for view_index, view_tables in tables_dict.items():
for detected_table in view_tables:
print(f"View {view_index}: {type(detected_table).__name__} from {detected_table.source}")
Only nb_rows, nb_cols, cells, source, bounding_rectangle and the
plot methods are common to both types. Everything the next section uses — nb_cells,
grid, get_table_cell_at, find_table_cells_with_text, non_empty_cells —
belongs to DetectedTable only, and a native Table’s cells holds nested
lists instead of TableCell objects. Filter by type before using the rich API:
from dessia_drawing.annotations.table import Table
from drawing_tools.table.detection import DetectedTable
reconstructed_tables = [t for view_tables in tables_dict.values() for t in view_tables
if isinstance(t, DetectedTable)]
native_tables = [t for view_tables in tables_dict.values() for t in view_tables
if isinstance(t, Table)]
Note
Only the background view contributes its own geometries to the edge-based
branch (minus the grid reference, so the sheet frame is never taken for a table).
For the other views, only the geometries carried by CompositeEntity annotations
are candidates — their direct geometries draw the part, not tables.
Working with Detected Tables#
The API below is that of DetectedTable (the reconstructed tables of the previous
section); a native Table annotation offers only nb_rows / nb_cols / cells.
Table Properties#
# Table dimensions
print(f"Rows: {table.nb_rows}")
print(f"Columns: {table.nb_cols}")
print(f"Cells: {table.nb_cells}")
# Access all cells
for cell in table.cells:
print(f"Cell ({cell.row_id}, {cell.col_id}): {cell.get_text_content()}")
TableCell vs GridCell#
Tables have two cell concepts:
GridCell: The basic grid unit (single row/column intersection) — see Fig. 3 below
TableCell: A logical cell that may span multiple grid cells (merged cells) — see Fig. 2 below
# Get a table cell (may span multiple grid cells)
table_cell = table.get_table_cell_at(row_index=1, column_index=1)
if table_cell:
print(f"Content: {table_cell.get_text_content()}")
print(f"Combined text: {table_cell.get_combined_text()}")
print(f"Row span: {table_cell.row_span}")
print(f"Column span: {table_cell.column_span}")
# Get the underlying grid cell
grid_cell = table.get_grid_cell_at(row_index=1, column_index=1)
Accessing Cell Content#
# All content objects from all cells
all_content = table.get_all_content()
# Iterate over cells
for cell in table.cells:
content = cell.get_text_content() or "(empty)"
print(f"({cell.row_id}, {cell.col_id}): {content}")
# Search for specific text
cells = table.find_table_cells_with_text("DIMENSION", case_sensitive=False)
for cell in cells:
print(f"Found at ({cell.row_id}, {cell.col_id})")
Working with Title Blocks#
title_block = detector.detect_title_block()
table = title_block.parent_table # DetectedTable with all methods above
Visualizing a Single Table#
By default each cell cycles through a rotating palette: its texts are drawn in the cell
color and its background in the same color lightened, so the pairing between a text and
its cell is visible at a glance (a colored cell with no text of its color is an empty
cell). To inspect merged cells, use detailed=True: each cell label carries its
position and, when merged, the spanned grid ranges.
# Basic visualization — one color per cell (texts + lightened background)
table.plot_data()
# Detailed view with cell position labels
table.plot_data(detailed=True)
# Visualize the underlying grid
table.grid.plot_data(extra_primitives=[])
Visualizing a Whole Sheet#
Use the @plot_data_view method on FeaturedSheet to visualize every detected
table of the sheet at once (all kinds together, title block labeled in magenta).
The rendering goes through FeaturedSheet.table_detector — call
featured_sheet.set_table_detector(my_detector) with your own SheetTableDetector
to render a non-default-configured detection:
# All detected tables with title block highlighted
featured_sheet.plot_data_tables().plot()
This method is also available on FeaturedDrawing to visualize all sheets at once.
See Display & Report Methods for more interactive examples.
Going Further: Inside the Detection#
For readers who want to see how the detector works
The feature is DetectedTable (table/detection.py): a grid of TableLine
objects, the TableCell and GridCell of table/core.py built on it, and the
content attached to each cell. The title block is the same table read further:
TitleBlock (title_block.py) holds its parsed fields.
Two detectors share the work, and FeaturedSheet.table_detector owns them:
SheetTableDetector(table/detection.py) walks the views of a sheet and merges the three sources of The Different Kinds of Tables;TableGenerator(same module) is the edge-based branch — the one that rebuilds a table from bare lines — andTitleBlockExtractor(table/title_block.py) reads the fields of the title-block table found bydetect_title_block.
The recipe is one loop over the views:
def detect_all_tables(self) -> dict[int, list[TableType]]:
"""
Detect all tables in the drawing across all views.
Every view goes through the three branches of :meth:`detect_view_tables`
(``Table`` annotations, framed ``Symbol`` notes, edge-based detection).
The background view gets ONE extra candidate source: its own ``geometries``
(the sheet frame layer, where the title block and the revision block live),
minus the grid reference geometries. For the other views only the geometries
carried by ``CompositeEntity`` annotations are candidates — their direct
geometries draw the part, not tables.
Edge-based tables are validated to ensure they have sufficient content before
being included.
:return: Dictionary mapping view index to list of validated detected tables
"""
all_tables = {}
for view_idx, view in enumerate(self.sheet.views):
is_background = view_idx == self.background_view_index
view_tables = self.detect_view_tables(view=view, is_background=is_background, _view_index=view_idx)
if view_tables:
all_tables[view_idx] = view_tables
return all_tables
detect_view_tables runs the three branches on one view: _extract_tables_from_tables
(native Table annotations), _extract_tables_from_notes (TypeNote symbols
with a rectangular frame, _table_from_framed_entity) and
_extract_tables_from_geometries, which hands the candidate edges to
TableGenerator.detect_tables_from_edges — _preprocess_lines merges the collinear
segments, _establish_connections links the lines that touch, and
_find_table_structures turns each connected component with enough content into a
DetectedTable. The full pipeline, stage by stage, is in
Table Detection Pipeline.
Related Guides#
Featured Classes Guide - Using FeaturedSheet with table detection
Table Detection Pipeline - Detection pipeline internals
Display & Report Methods - Interactive display examples
Related Classes#
SheetTableDetector: Main table detection classDetectedTable: Reconstructed table structureTableCell: Individual table cell (may span several grid cells)GridCell: Basic grid unitTableGrid: The grid a reconstructed table is built onTitleBlock: Structured fields parsed from the title block
For more information, see the API Reference.