Skip to content

Commit dd33585

Browse files
committed
fix: default config by size
1 parent a7471fb commit dd33585

18 files changed

Lines changed: 1278 additions & 1055 deletions

File tree

‎extract-core/extract_core/__init__.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
MarkdownDoc,
1414
OutputFormat,
1515
Pages,
16+
PipelineSize,
1617
Ranges,
1718
Result,
1819
Status,
@@ -77,6 +78,7 @@
7778
"Ranges",
7879
"Pages",
7980
"Pipeline",
81+
"PipelineSize",
8082
"PipelineType",
8183
"Result",
8284
"Status",
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from .cli import cli_app
2+
3+
4+
def main() -> None:
5+
cli_app()
6+
7+
8+
if __name__ == "__main__":
9+
main()
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import importlib
2+
import os
3+
from typing import Annotated
4+
5+
import typer
6+
from icij_common.logging_utils import setup_loggers
7+
8+
import extract_core
9+
10+
from .configs import configs_app
11+
from .utils import AsyncTyper
12+
13+
cli_app = AsyncTyper(
14+
context_settings={"help_option_names": ["-h", "--help"]},
15+
pretty_exceptions_enable=False,
16+
)
17+
cli_app.add_typer(configs_app)
18+
19+
20+
def version_callback(value: bool) -> None: # noqa: FBT001
21+
if value:
22+
package_version = importlib.metadata.version(extract_core.__name__)
23+
print(package_version)
24+
raise typer.Exit()
25+
26+
27+
def pretty_exc_callback(value: bool) -> None: # noqa: FBT001
28+
if not value:
29+
os.environ["TYPER_STANDARD_TRACEBACK"] = "1"
30+
31+
32+
@cli_app.callback()
33+
def main(
34+
version: Annotated[ # noqa: ARG001
35+
bool | None,
36+
typer.Option("--version", callback=version_callback, is_eager=True),
37+
] = None,
38+
*,
39+
pretty_exceptions: Annotated[ # noqa: ARG001
40+
bool,
41+
typer.Option(
42+
"--pretty-exceptions", callback=pretty_exc_callback, is_eager=True
43+
),
44+
] = False,
45+
) -> None:
46+
setup_loggers(["__main__", extract_core.__name__])
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from typing import Annotated
2+
3+
import typer
4+
5+
from extract_core import PipelineSize
6+
7+
from ..configs import PipelineType
8+
from ..default_config import default_config
9+
from ..objects import Device
10+
from .utils import AsyncTyper
11+
12+
_CONFIGS = "config"
13+
14+
_DEFAULT_CONFIG_HELP = (
15+
"display default configuration for a given pipeline type,"
16+
" device and computing power"
17+
)
18+
_DEFAULT_CONFIG_DEVICE_HELP = "device for which the default config should be displayed"
19+
_DEFAULT_CONFIG_SIZE_HELP = "pipeline size, smaller is faster"
20+
21+
configs_app = AsyncTyper(name=_CONFIGS)
22+
23+
24+
@configs_app.async_command(name="get-default", help=_DEFAULT_CONFIG_HELP)
25+
async def display_default_config(
26+
pipeline_type: PipelineType,
27+
device: Annotated[
28+
Device,
29+
typer.Option("-d", "--device", help=_DEFAULT_CONFIG_DEVICE_HELP),
30+
] = Device.CUDA,
31+
size: Annotated[
32+
PipelineSize,
33+
typer.Option("-s", "--size", help=_DEFAULT_CONFIG_SIZE_HELP),
34+
] = PipelineSize.SMALL,
35+
) -> None:
36+
config = default_config(pipeline_type=pipeline_type, device=device, size=size)
37+
print(config.model_dump_json(indent=2))
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import asyncio
2+
import sys
3+
from collections.abc import Callable
4+
from functools import wraps
5+
from typing import Any
6+
7+
import typer
8+
9+
10+
class AsyncTyper(typer.Typer):
11+
def async_command(self, *args, **kwargs) -> Callable[[Callable], Callable]:
12+
def decorator(async_func: Callable) -> Callable:
13+
@wraps(async_func)
14+
def sync_func(*_args, **_kwargs) -> Any:
15+
res = asyncio.run(async_func(*_args, **_kwargs))
16+
return res
17+
18+
self.command(*args, **kwargs)(sync_func)
19+
return async_func
20+
21+
return decorator
22+
23+
24+
def eprint(*args, **kwargs) -> None:
25+
print(*args, file=sys.stderr, **kwargs)
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
from .configs import BasePipelineConfig, Device, PipelineType
2+
from .objects import PipelineSize
3+
4+
5+
def default_config(
6+
*, pipeline_type: PipelineType, device: Device, size: PipelineSize
7+
) -> BasePipelineConfig:
8+
match pipeline_type:
9+
case PipelineType.DOCLING:
10+
return _default_docling_config(device=device, size=size)
11+
case PipelineType.MARKER:
12+
return _default_marker_config(size)
13+
case PipelineType.MINER_U:
14+
return _default_mineru_config(size)
15+
case _:
16+
raise NotImplementedError(f"unsupported pipeline type {pipeline_type}")
17+
18+
19+
def _default_docling_config(
20+
*, device: Device, size: PipelineSize
21+
) -> BasePipelineConfig:
22+
from .docling_ import DoclingPipelineConfig, default_format_opts # noqa: PLC0415
23+
24+
format_opts = default_format_opts(device=device, size=size)
25+
pipeline_cfg = DoclingPipelineConfig(format_options=format_opts)
26+
return pipeline_cfg
27+
28+
29+
def _default_marker_config(size: PipelineSize) -> BasePipelineConfig:
30+
from .marker_ import MarkerPipelineConfig # noqa: PLC0415
31+
32+
mode = "balanced" if size is PipelineSize.LARGE else "fast"
33+
return MarkerPipelineConfig(config={"mode": mode})
34+
35+
36+
def _default_mineru_config(size: PipelineSize) -> BasePipelineConfig:
37+
from .miner_u import MinerUConfig, MinerUPipelineConfig # noqa: PLC0415
38+
39+
match size:
40+
case PipelineSize.SMALL:
41+
tier = "basic"
42+
case PipelineSize.MEDIUM:
43+
tier = "standard"
44+
case PipelineSize.LARGE:
45+
tier = "advanced"
46+
case _:
47+
raise TypeError(f"unsupported pipeline size {size}")
48+
return MinerUPipelineConfig(config=MinerUConfig(tier=tier))

‎extract-core/extract_core/docling_.py‎

Lines changed: 55 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import importlib
2+
from copy import deepcopy
23
from functools import cache
34
from typing import TYPE_CHECKING, Annotated, Any, ClassVar, get_type_hints
45

6+
from docling.backend.image_backend import ImageDocumentBackend
57
from docling.datamodel.backend_options import BackendOptions, BaseBackendOptions
68
from docling.datamodel.base_models import (
79
BaseFormatOption,
@@ -20,6 +22,15 @@
2022
)
2123
from docling.datamodel.settings import DebugSettings
2224
from docling.datamodel.settings import InferenceSettings as DoclingInferenceSettings
25+
from docling.document_converter import (
26+
BoxNoteFormatOption,
27+
CsvFormatOption,
28+
ExcelFormatOption,
29+
OdsFormatOption,
30+
OdtFormatOption,
31+
PowerpointFormatOption,
32+
WordFormatOption,
33+
)
2334
from icij_common.pydantic_utils import (
2435
merge_configs,
2536
safe_copy,
@@ -31,7 +42,7 @@
3142
from pydantic_core.core_schema import SerializerFunctionWrapHandler
3243

3344
from .configs import BasePipelineConfig, PipelineType, ResultBufferConfig
34-
from .objects import BaseModel, Device, SupportedExt
45+
from .objects import BaseModel, Device, PipelineSize, SupportedExt
3546
from .utils import all_subclasses
3647

3748
if TYPE_CHECKING:
@@ -233,7 +244,48 @@ def to_docling(self, device: Device) -> BaseFormatOption: # noqa: ANN201
233244
)
234245

235246

236-
def _default_format_opts() -> dict[InputFormat, DoclingFormatOption]:
247+
def default_format_opts(
248+
*, size: PipelineSize = PipelineSize.SMALL, device: Device = Device.CPU
249+
) -> dict[InputFormat, DoclingFormatOption]:
250+
from docling.backend.docling_parse_backend import ( # noqa: PLC0415
251+
ThreadedDoclingParseDocumentBackend,
252+
)
253+
from docling.pipeline.threaded_standard_pdf_pipeline import ( # noqa: PLC0415
254+
ThreadedStandardPdfPipeline,
255+
)
256+
from docling.pipeline.vlm_pipeline import VlmPipeline # noqa: PLC0415
257+
258+
default = _default_format_options()
259+
accelerator_opts = deepcopy(default[InputFormat.PDF].pipeline_options)[
260+
"accelerator_options"
261+
]
262+
accelerator_opts["device"] = device.to_docling()
263+
pdf_pipeline_opts = default[InputFormat.PDF].pipeline_options
264+
pdf_pipeline_opts["accelerator_options"] = accelerator_opts
265+
match size:
266+
case PipelineSize.LARGE:
267+
pipeline = VlmPipeline.__name__
268+
case _:
269+
pipeline = ThreadedStandardPdfPipeline.__name__
270+
backend_opts = default[InputFormat.PDF].backend_options
271+
pdf_fmt_opts = DoclingFormatOption(
272+
pipeline_options=pdf_pipeline_opts,
273+
pipeline_cls=pipeline,
274+
backend=ThreadedDoclingParseDocumentBackend.__name__,
275+
backend_options=backend_opts,
276+
)
277+
default[InputFormat.PDF] = pdf_fmt_opts
278+
image_fmt_opts = DoclingFormatOption(
279+
pipeline_options=deepcopy(pdf_pipeline_opts),
280+
pipeline_cls=pipeline,
281+
backend=ImageDocumentBackend.__name__,
282+
backend_options=deepcopy(backend_opts),
283+
)
284+
default[InputFormat.IMAGE] = image_fmt_opts
285+
return default
286+
287+
288+
def _default_format_options() -> dict[InputFormat, DoclingFormatOption]:
237289
from docling.backend.json.docling_json_backend import ( # noqa: PLC0415
238290
DoclingJSONBackend,
239291
)
@@ -242,27 +294,20 @@ def _default_format_opts() -> dict[InputFormat, DoclingFormatOption]:
242294
from docling.document_converter import ( # noqa: PLC0415 # noqa: PLC0415
243295
AsciiDocFormatOption,
244296
AudioFormatOption,
245-
BoxNoteFormatOption,
246-
CsvFormatOption,
247297
DclxFormatOption,
248298
EbcdicFormatOption,
249299
EmailFormatOption,
250300
EpubFormatOption,
251-
ExcelFormatOption,
252301
FormatOption,
253302
HTMLFormatOption,
254303
ImageFormatOption,
255304
IWorkPagesFormatOption,
256305
LatexFormatOption,
257306
MarkdownFormatOption,
258307
OdpFormatOption,
259-
OdsFormatOption,
260-
OdtFormatOption,
261308
PatentUsptoFormatOption,
262309
PdfFormatOption,
263-
PowerpointFormatOption,
264310
VideoFormatOption,
265-
WordFormatOption,
266311
XBRLFormatOption,
267312
XMLDocLangFormatOption,
268313
XMLJatsFormatOption,
@@ -361,7 +406,7 @@ class DoclingPipelineConfig(BasePipelineConfig):
361406
pipeline: ClassVar[PipelineType] = Field(frozen=True, default=PipelineType.DOCLING)
362407

363408
format_options: dict[InputFormat, DoclingFormatOption] = Field(
364-
default_factory=_default_format_opts
409+
default_factory=default_format_opts
365410
)
366411

367412
settings: DoclingSettings = Field(default_factory=DoclingSettings)

‎extract-core/extract_core/miner_u.py‎

Lines changed: 8 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
from collections.abc import Callable
2-
from copy import copy
32
from enum import StrEnum
43
from functools import cache
5-
from typing import Any, ClassVar
4+
from typing import ClassVar, Literal
65

6+
from mineru.config import VlmConfig
7+
from mineru.types import Tier
78
from pydantic import Field
89
from pydantic_extra_types.language_code import LanguageAlpha2
910

@@ -21,48 +22,17 @@ class MinerUBackend(StrEnum):
2122

2223
class MinerUConfig(BaseModel):
2324
backend: MinerUBackend = MinerUBackend.PIPELINE
24-
enable_formula_extraction: bool = True
25-
enable_table_extraction: bool = True
26-
# TODO: use enum or literal here
27-
parse_method: str = "auto"
2825

29-
def as_parse_kwargs(self) -> dict[str, Any]:
30-
kwargs = copy(self._get_default_kwargs())
31-
kwargs["backend"] = self.backend
32-
kwargs["parse_method"] = self.parse_method
33-
kwargs["formula_enable"] = self.enable_formula_extraction
34-
kwargs["table_enable"] = self.enable_table_extraction
35-
return kwargs
36-
37-
@classmethod
38-
@cache
39-
def _get_default_kwargs(cls) -> dict[str, Any]:
40-
from mineru.utils.enum_class import MakeMode # noqa: PLC0415
41-
42-
return {
43-
"server_url": None,
44-
# We don't dump md directly we process, we dump the middle json in order
45-
# to be able to get page indexes
46-
"parse_method": "auto",
47-
"dump_md": False,
48-
"dump_middle_json": True,
49-
"f_draw_layout_bbox": False,
50-
"f_draw_span_bbox": False,
51-
"f_dump_model_output": False, # might be useful for debug though
52-
"f_dump_orig_pdf": False,
53-
"f_dump_content_list": False, # might be useful for debug though
54-
"start_page_id": 0,
55-
"f_make_md_mode": MakeMode.MM_MD,
56-
"image_analysis": True,
57-
"end_page_id": None,
58-
"client_side_output_generation": False,
59-
}
26+
tier: Tier = "basic"
27+
parse_mode: Literal["auto", "txt", "ocr"] = "auto"
28+
image_analysis: bool = True
29+
vlm_config: VlmConfig | None = None
6030

6131

6232
class MinerUPipelineConfig(BasePipelineConfig): # noqa: F821
6333
pipeline: ClassVar[PipelineType] = Field(frozen=True, default=PipelineType.MINER_U)
6434

65-
config: MinerUConfig = Field(frozen=True, default=MinerUConfig())
35+
config: MinerUConfig = Field(frozen=True, default_factory=MinerUConfig)
6636
language: LanguageAlpha2 = Field(frozen=True, default="en")
6737

6838
@classmethod

‎extract-core/extract_core/objects.py‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ def to_docling(self) -> AcceleratorDevice:
4343
raise ValueError(f"unsupported device {self}")
4444

4545

46+
class PipelineSize(StrEnum):
47+
SMALL = "small"
48+
MEDIUM = "medium"
49+
LARGE = "large"
50+
51+
4652
class SupportedExt(StrEnum):
4753
ADOC = ".adoc"
4854
AFP = ".afp"

0 commit comments

Comments
 (0)