修改为东南天坐标系
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,113 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from test_unstructured.unit_utils import example_doc_path
|
||||
from unstructured.metrics.element_type import (
|
||||
FrequencyDict,
|
||||
calculate_element_type_percent_match,
|
||||
get_element_type_frequency,
|
||||
)
|
||||
from unstructured.partition.auto import partition
|
||||
from unstructured.staging.base import elements_to_json
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("filename", "frequency"),
|
||||
[
|
||||
(
|
||||
"fake-email.txt",
|
||||
{
|
||||
("NarrativeText", None): 1,
|
||||
("UncategorizedText", None): 1,
|
||||
("ListItem", 1): 2,
|
||||
},
|
||||
),
|
||||
(
|
||||
"sample-presentation.pptx",
|
||||
{
|
||||
("Title", 0): 4,
|
||||
("Title", 1): 1,
|
||||
("NarrativeText", 0): 3,
|
||||
("PageBreak", None): 3,
|
||||
("ListItem", 0): 6,
|
||||
("ListItem", 1): 6,
|
||||
("ListItem", 2): 3,
|
||||
("Table", None): 1,
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_get_element_type_frequency(filename: str, frequency: dict[tuple[str, int | None], int]):
|
||||
elements = partition(example_doc_path(filename))
|
||||
elements_freq = get_element_type_frequency(elements_to_json(elements))
|
||||
assert elements_freq == frequency
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("filename", "expected_frequency", "percent_matched"),
|
||||
[
|
||||
(
|
||||
"fake-email.txt",
|
||||
{
|
||||
("UncategorizedText", None): 1,
|
||||
("ListItem", 1): 2,
|
||||
("NarrativeText", None): 2,
|
||||
},
|
||||
(0.8, 0.8, 0.80),
|
||||
),
|
||||
(
|
||||
"sample-presentation.pptx",
|
||||
{
|
||||
("Title", 0): 3,
|
||||
("Title", 1): 1,
|
||||
("NarrativeText", None): 1,
|
||||
("NarrativeText", 0): 3,
|
||||
("ListItem", 0): 6,
|
||||
("ListItem", 1): 6,
|
||||
("ListItem", 2): 3,
|
||||
("Table", None): 1,
|
||||
},
|
||||
(0.96, 0.96, 0.96),
|
||||
),
|
||||
(
|
||||
"handbook-1p.docx",
|
||||
{
|
||||
("Header", None): 1,
|
||||
("UncategorizedText", 0): 6,
|
||||
("ListItem", 3): 3,
|
||||
("NarrativeText", 0): 7,
|
||||
("Footer", None): 1,
|
||||
},
|
||||
(0.78, 0.72, 0.81),
|
||||
),
|
||||
(
|
||||
"handbook-1p.docx",
|
||||
{
|
||||
("Header", None): 1,
|
||||
("UncategorizedText", 0): 6,
|
||||
("NarrativeText", 0): 7,
|
||||
("PageBreak", None): 1,
|
||||
("Footer", None): 1,
|
||||
},
|
||||
(0.94, 0.88, 0.98),
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_calculate_element_type_percent_match(
|
||||
filename: str, expected_frequency: FrequencyDict, percent_matched: tuple[float, float, float]
|
||||
):
|
||||
elements = partition(example_doc_path(filename))
|
||||
elements_frequency = get_element_type_frequency(elements_to_json(elements))
|
||||
assert (
|
||||
round(calculate_element_type_percent_match(elements_frequency, expected_frequency), 2)
|
||||
== percent_matched[0]
|
||||
)
|
||||
assert (
|
||||
round(calculate_element_type_percent_match(elements_frequency, expected_frequency, 0.0), 2)
|
||||
== percent_matched[1]
|
||||
)
|
||||
assert (
|
||||
round(calculate_element_type_percent_match(elements_frequency, expected_frequency, 0.8), 2)
|
||||
== percent_matched[2]
|
||||
)
|
||||
@@ -0,0 +1,595 @@
|
||||
import os
|
||||
import pathlib
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import pytest
|
||||
|
||||
from unstructured.metrics.evaluate import (
|
||||
ElementTypeMetricsCalculator,
|
||||
TableStructureMetricsCalculator,
|
||||
TextExtractionMetricsCalculator,
|
||||
filter_metrics,
|
||||
get_mean_grouping,
|
||||
)
|
||||
|
||||
is_in_docker = os.path.exists("/.dockerenv")
|
||||
|
||||
EXAMPLE_DOCS_DIRECTORY = os.path.join(
|
||||
pathlib.Path(__file__).parent.resolve(), "..", "..", "example-docs"
|
||||
)
|
||||
TESTING_FILE_DIR = os.path.join(EXAMPLE_DOCS_DIRECTORY, "test_evaluate_files")
|
||||
|
||||
UNSTRUCTURED_OUTPUT_DIRNAME = "unstructured_output"
|
||||
GOLD_CCT_DIRNAME = "gold_standard_cct"
|
||||
GOLD_ELEMENT_TYPE_DIRNAME = "gold_standard_element_type"
|
||||
GOLD_TABLE_STRUCTURE_DIRNAME = "gold_standard_table_structure"
|
||||
UNSTRUCTURED_CCT_DIRNAME = "unstructured_output_cct"
|
||||
UNSTRUCTURED_TABLE_STRUCTURE_DIRNAME = "unstructured_output_table_structure"
|
||||
|
||||
DUMMY_DF_CCT = pd.DataFrame(
|
||||
{
|
||||
"filename": [
|
||||
"Bank Good Credit Loan.pptx",
|
||||
"Performance-Audit-Discussion.pdf",
|
||||
"currency.csv",
|
||||
],
|
||||
"doctype": ["pptx", "pdf", "csv"],
|
||||
"connector": ["connector1", "connector1", "connector2"],
|
||||
"cct-accuracy": [0.812, 0.994, 0.887],
|
||||
"cct-%missing": [0.001, 0.002, 0.041],
|
||||
}
|
||||
)
|
||||
|
||||
DUMMY_DF_ELEMENT_TYPE = pd.DataFrame(
|
||||
{
|
||||
"filename": [
|
||||
"Bank Good Credit Loan.pptx",
|
||||
"Performance-Audit-Discussion.pdf",
|
||||
"currency.csv",
|
||||
],
|
||||
"doctype": ["pptx", "pdf", "csv"],
|
||||
"connector": ["connector1", "connector1", "connector2"],
|
||||
"element-type-accuracy": [0.812, 0.994, 0.887],
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_dependencies():
|
||||
with patch(
|
||||
"unstructured.metrics.evaluate.calculate_accuracy"
|
||||
) as mock_calculate_accuracy, patch(
|
||||
"unstructured.metrics.evaluate.calculate_percent_missing_text"
|
||||
) as mock_calculate_percent_missing_text, patch.object(
|
||||
TextExtractionMetricsCalculator, "_get_ccts"
|
||||
) as mock_get_ccts, patch(
|
||||
"unstructured.metrics.evaluate.get_element_type_frequency"
|
||||
) as mock_get_element_type_frequency, patch(
|
||||
"unstructured.metrics.evaluate.calculate_element_type_percent_match"
|
||||
) as mock_calculate_element_type_percent_match, patch(
|
||||
"unstructured.metrics.evaluate._read_text_file"
|
||||
) as mock_read_text_file, patch.object(
|
||||
Path, "exists"
|
||||
) as mock_path_exists, patch(
|
||||
"unstructured.metrics.evaluate.TableEvalProcessor.from_json_files"
|
||||
) as mock_table_eval_processor_from_json_files, patch.object(
|
||||
TableStructureMetricsCalculator, "supported_metric_names"
|
||||
) as mock_supported_metric_names:
|
||||
mocks = {
|
||||
"mock_calculate_accuracy": mock_calculate_accuracy,
|
||||
"mock_calculate_percent_missing_text": mock_calculate_percent_missing_text,
|
||||
"mock_get_ccts": mock_get_ccts,
|
||||
"mock_get_element_type_frequency": mock_get_element_type_frequency,
|
||||
"mock_read_text_file": mock_read_text_file,
|
||||
"mock_calculate_element_type_percent_match": mock_calculate_element_type_percent_match,
|
||||
"mock_table_eval_processor_from_json_files": mock_table_eval_processor_from_json_files,
|
||||
"mock_supported_metric_names": mock_supported_metric_names,
|
||||
"mock_path_exists": mock_path_exists,
|
||||
}
|
||||
|
||||
# setup mocks
|
||||
mocks["mock_calculate_accuracy"].return_value = 0.5
|
||||
mocks["mock_calculate_percent_missing_text"].return_value = 0.5
|
||||
mocks["mock_get_ccts"].return_value = ["output_cct", "source_cct"]
|
||||
mocks["mock_get_element_type_frequency"].side_effect = [{"ele1": 1}, {"ele2": 3}]
|
||||
mocks["mock_calculate_element_type_percent_match"].return_value = 0.5
|
||||
mocks["mock_supported_metric_names"].return_value = ["table_level_acc"]
|
||||
mocks["mock_path_exists"].return_value = True
|
||||
mocks["mock_read_text_file"].side_effect = ["output_text", "source_text"]
|
||||
|
||||
yield mocks
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def _cleanup_after_test():
|
||||
"""Fixture for removing side-effects of running tests in this file."""
|
||||
|
||||
def remove_generated_directories():
|
||||
"""Remove directories created from running tests."""
|
||||
|
||||
# Directories to be removed:
|
||||
target_dir_names = [
|
||||
"test_evaluate_results_cct",
|
||||
"test_evaluate_results_cct_txt",
|
||||
"test_evaluate_results_element_type",
|
||||
"test_evaluate_result_table_structure",
|
||||
]
|
||||
subdirs = (d for d in os.scandir(TESTING_FILE_DIR) if d.is_dir())
|
||||
for d in subdirs:
|
||||
if d.name in target_dir_names:
|
||||
shutil.rmtree(d.path)
|
||||
|
||||
# Run test as normal
|
||||
yield
|
||||
remove_generated_directories()
|
||||
|
||||
|
||||
@pytest.mark.skipif(is_in_docker, reason="Skipping this test in Docker container")
|
||||
@pytest.mark.usefixtures("_cleanup_after_test")
|
||||
def test_text_extraction_evaluation():
|
||||
output_dir = os.path.join(TESTING_FILE_DIR, UNSTRUCTURED_OUTPUT_DIRNAME)
|
||||
source_dir = os.path.join(TESTING_FILE_DIR, GOLD_CCT_DIRNAME)
|
||||
export_dir = os.path.join(TESTING_FILE_DIR, "test_evaluate_results_cct")
|
||||
|
||||
TextExtractionMetricsCalculator(
|
||||
documents_dir=output_dir, ground_truths_dir=source_dir
|
||||
).calculate(export_dir=export_dir, visualize_progress=False, display_agg_df=False)
|
||||
|
||||
assert os.path.isfile(os.path.join(export_dir, "all-docs-cct.tsv"))
|
||||
df = pd.read_csv(os.path.join(export_dir, "all-docs-cct.tsv"), sep="\t")
|
||||
assert len(df) == 3
|
||||
assert len(df.columns) == 5
|
||||
assert df.iloc[0].filename == "Bank Good Credit Loan.pptx"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("calculator_class", "output_dirname", "source_dirname", "path", "expected_length", "kwargs"),
|
||||
[
|
||||
(
|
||||
TextExtractionMetricsCalculator,
|
||||
UNSTRUCTURED_CCT_DIRNAME,
|
||||
GOLD_CCT_DIRNAME,
|
||||
Path("Bank Good Credit Loan.pptx.txt"),
|
||||
5,
|
||||
{"document_type": "txt"},
|
||||
),
|
||||
(
|
||||
TableStructureMetricsCalculator,
|
||||
UNSTRUCTURED_TABLE_STRUCTURE_DIRNAME,
|
||||
GOLD_TABLE_STRUCTURE_DIRNAME,
|
||||
Path("IRS-2023-Form-1095-A.pdf.json"),
|
||||
14,
|
||||
{},
|
||||
),
|
||||
(
|
||||
ElementTypeMetricsCalculator,
|
||||
UNSTRUCTURED_OUTPUT_DIRNAME,
|
||||
GOLD_ELEMENT_TYPE_DIRNAME,
|
||||
Path("IRS-form-1987.pdf.json"),
|
||||
4,
|
||||
{},
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_process_document_returns_the_correct_amount_of_values(
|
||||
calculator_class, output_dirname, source_dirname, path, expected_length, kwargs
|
||||
):
|
||||
output_dir = Path(TESTING_FILE_DIR) / output_dirname
|
||||
source_dir = Path(TESTING_FILE_DIR) / source_dirname
|
||||
|
||||
calculator = calculator_class(documents_dir=output_dir, ground_truths_dir=source_dir, **kwargs)
|
||||
output_list = calculator._process_document(path)
|
||||
assert len(output_list) == expected_length
|
||||
|
||||
|
||||
@pytest.mark.skipif(is_in_docker, reason="Skipping this test in Docker container")
|
||||
@pytest.mark.usefixtures("_cleanup_after_test", "mock_dependencies")
|
||||
@pytest.mark.parametrize(
|
||||
("calculator_class", "output_dirname", "source_dirname", "path", "kwargs"),
|
||||
[
|
||||
(
|
||||
TextExtractionMetricsCalculator,
|
||||
UNSTRUCTURED_CCT_DIRNAME,
|
||||
GOLD_CCT_DIRNAME,
|
||||
Path("2310.03502text_to_image_synthesis1-7.pdf.txt"),
|
||||
{"document_type": "txt"},
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_TextExtractionMetricsCalculator_process_document_returns_the_correct_doctype(
|
||||
mock_dependencies, calculator_class, output_dirname, source_dirname, path, kwargs
|
||||
):
|
||||
output_dir = Path(TESTING_FILE_DIR) / output_dirname
|
||||
source_dir = Path(TESTING_FILE_DIR) / source_dirname
|
||||
mock_calculate_accuracy = mock_dependencies["mock_calculate_accuracy"]
|
||||
mock_calculate_percent_missing_text = mock_dependencies["mock_calculate_percent_missing_text"]
|
||||
mock_get_ccts = mock_dependencies["mock_get_ccts"]
|
||||
calculator = calculator_class(documents_dir=output_dir, ground_truths_dir=source_dir, **kwargs)
|
||||
output_list = calculator._process_document(path)
|
||||
assert output_list[1] == ".pdf"
|
||||
assert mock_calculate_accuracy.call_count == 1
|
||||
assert mock_calculate_percent_missing_text.call_count == 1
|
||||
assert mock_get_ccts.call_count == 1
|
||||
|
||||
|
||||
@pytest.mark.skipif(is_in_docker, reason="Skipping this test in Docker container")
|
||||
@pytest.mark.usefixtures("_cleanup_after_test", "mock_dependencies")
|
||||
@pytest.mark.parametrize(
|
||||
("calculator_class", "output_dirname", "source_dirname", "path", "kwargs"),
|
||||
[
|
||||
(
|
||||
TableStructureMetricsCalculator,
|
||||
UNSTRUCTURED_TABLE_STRUCTURE_DIRNAME,
|
||||
GOLD_TABLE_STRUCTURE_DIRNAME,
|
||||
Path("tablib-627mTABLES-2310.07875-p7.pdf.json"),
|
||||
{},
|
||||
),
|
||||
# (
|
||||
# ElementTypeMetricsCalculator,
|
||||
# UNSTRUCTURED_OUTPUT_DIRNAME,
|
||||
# GOLD_ELEMENT_TYPE_DIRNAME,
|
||||
# Path("IRS-form.1987.pdf.json"),
|
||||
# {},
|
||||
# ),
|
||||
],
|
||||
)
|
||||
def test_TableStructureMetricsCalculator_process_document_returns_the_correct_doctype(
|
||||
mock_dependencies, calculator_class, output_dirname, source_dirname, path, kwargs
|
||||
):
|
||||
output_dir = Path(TESTING_FILE_DIR) / output_dirname
|
||||
source_dir = Path(TESTING_FILE_DIR) / source_dirname
|
||||
calculator = calculator_class(documents_dir=output_dir, ground_truths_dir=source_dir, **kwargs)
|
||||
calculator._ground_truths_dir = source_dir
|
||||
calculator._documents_dir = output_dir
|
||||
calculator._ground_truth_paths = [source_dir / path]
|
||||
mock_report = MagicMock()
|
||||
mock_report.total_predicted_tables = 3
|
||||
mock_report.table_evel_acc = 0.83
|
||||
mock_table_eval_processor_from_json_files = mock_dependencies[
|
||||
"mock_table_eval_processor_from_json_files"
|
||||
]
|
||||
mock_table_eval_processor_from_json_files.return_value.process_file.return_value = mock_report
|
||||
|
||||
output_list = calculator._process_document(path)
|
||||
assert output_list[1] == ".pdf"
|
||||
assert mock_table_eval_processor_from_json_files.call_count == 1
|
||||
|
||||
|
||||
@pytest.mark.skipif(is_in_docker, reason="Skipping this test in Docker container")
|
||||
@pytest.mark.usefixtures("_cleanup_after_test", "mock_dependencies")
|
||||
@pytest.mark.parametrize(
|
||||
("calculator_class", "output_dirname", "source_dirname", "path", "kwargs"),
|
||||
[
|
||||
(
|
||||
ElementTypeMetricsCalculator,
|
||||
UNSTRUCTURED_OUTPUT_DIRNAME,
|
||||
GOLD_ELEMENT_TYPE_DIRNAME,
|
||||
Path("IRS-form.1987.pdf.json"),
|
||||
{},
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_ElementTypeMetricsCalculator_process_document_returns_the_correct_doctype(
|
||||
mock_dependencies, calculator_class, output_dirname, source_dirname, path, kwargs
|
||||
):
|
||||
output_dir = Path(TESTING_FILE_DIR) / output_dirname
|
||||
source_dir = Path(TESTING_FILE_DIR) / source_dirname
|
||||
calculator = calculator_class(documents_dir=output_dir, ground_truths_dir=source_dir, **kwargs)
|
||||
mock_element_type_frequency = mock_dependencies["mock_get_element_type_frequency"]
|
||||
mock_read_text_file = mock_dependencies["mock_read_text_file"]
|
||||
mock_calculate_element_type_percent_match = mock_dependencies[
|
||||
"mock_calculate_element_type_percent_match"
|
||||
]
|
||||
output_list = calculator._process_document(path)
|
||||
assert output_list[1] == ".pdf"
|
||||
assert mock_read_text_file.call_count == 2
|
||||
assert mock_element_type_frequency.call_count == 2
|
||||
assert mock_calculate_element_type_percent_match.call_count == 1
|
||||
|
||||
|
||||
@pytest.mark.skipif(is_in_docker, reason="Skipping this test in Docker container")
|
||||
@pytest.mark.usefixtures("_cleanup_after_test")
|
||||
def test_text_extraction_evaluation_type_txt():
|
||||
output_dir = os.path.join(TESTING_FILE_DIR, UNSTRUCTURED_CCT_DIRNAME)
|
||||
source_dir = os.path.join(TESTING_FILE_DIR, GOLD_CCT_DIRNAME)
|
||||
export_dir = os.path.join(TESTING_FILE_DIR, "test_evaluate_results_cct")
|
||||
|
||||
TextExtractionMetricsCalculator(
|
||||
documents_dir=output_dir, ground_truths_dir=source_dir, document_type="txt"
|
||||
).calculate(export_dir=export_dir)
|
||||
|
||||
df = pd.read_csv(os.path.join(export_dir, "all-docs-cct.tsv"), sep="\t")
|
||||
assert len(df) == 3
|
||||
assert len(df.columns) == 5
|
||||
assert df.iloc[0].filename == "Bank Good Credit Loan.pptx"
|
||||
|
||||
|
||||
@pytest.mark.skipif(is_in_docker, reason="Skipping this test in Docker container")
|
||||
@pytest.mark.usefixtures("_cleanup_after_test")
|
||||
def test_element_type_evaluation():
|
||||
output_dir = os.path.join(TESTING_FILE_DIR, UNSTRUCTURED_OUTPUT_DIRNAME)
|
||||
source_dir = os.path.join(TESTING_FILE_DIR, GOLD_ELEMENT_TYPE_DIRNAME)
|
||||
export_dir = os.path.join(TESTING_FILE_DIR, "test_evaluate_results_cct")
|
||||
|
||||
ElementTypeMetricsCalculator(
|
||||
documents_dir=output_dir,
|
||||
ground_truths_dir=source_dir,
|
||||
).calculate(export_dir=export_dir, visualize_progress=False)
|
||||
|
||||
assert os.path.isfile(os.path.join(export_dir, "all-docs-element-type-frequency.tsv"))
|
||||
df = pd.read_csv(os.path.join(export_dir, "all-docs-element-type-frequency.tsv"), sep="\t")
|
||||
assert len(df) == 1
|
||||
assert len(df.columns) == 4
|
||||
assert df.iloc[0].filename == "IRS-form-1987.pdf"
|
||||
|
||||
|
||||
@pytest.mark.skipif(is_in_docker, reason="Skipping this test in Docker container")
|
||||
@pytest.mark.usefixtures("_cleanup_after_test")
|
||||
def test_table_structure_evaluation():
|
||||
output_dir = os.path.join(TESTING_FILE_DIR, UNSTRUCTURED_TABLE_STRUCTURE_DIRNAME)
|
||||
source_dir = os.path.join(TESTING_FILE_DIR, GOLD_TABLE_STRUCTURE_DIRNAME)
|
||||
export_dir = os.path.join(TESTING_FILE_DIR, "test_evaluate_result_table_structure")
|
||||
|
||||
TableStructureMetricsCalculator(
|
||||
documents_dir=output_dir,
|
||||
ground_truths_dir=source_dir,
|
||||
).calculate(export_dir=export_dir, visualize_progress=False)
|
||||
|
||||
assert os.path.isfile(os.path.join(export_dir, "all-docs-table-structure-accuracy.tsv"))
|
||||
assert os.path.isfile(os.path.join(export_dir, "aggregate-table-structure-accuracy.tsv"))
|
||||
df = pd.read_csv(os.path.join(export_dir, "all-docs-table-structure-accuracy.tsv"), sep="\t")
|
||||
agg_df = pd.read_csv(
|
||||
os.path.join(export_dir, "aggregate-table-structure-accuracy.tsv"), sep="\t"
|
||||
).set_index("metric")
|
||||
assert len(df) == 2
|
||||
assert len(df.columns) == 15
|
||||
assert df.iloc[1].filename == "IRS-2023-Form-1095-A.pdf"
|
||||
assert (
|
||||
np.round(np.average(df["table_level_acc"], weights=df["total_tables"]), 3)
|
||||
== agg_df.loc["table_level_acc", "average"]
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.skipif(is_in_docker, reason="Skipping this test in Docker container")
|
||||
@pytest.mark.usefixtures("_cleanup_after_test")
|
||||
def test_text_extraction_takes_list():
|
||||
output_dir = os.path.join(TESTING_FILE_DIR, UNSTRUCTURED_OUTPUT_DIRNAME)
|
||||
output_list = ["currency.csv.json"]
|
||||
source_dir = os.path.join(TESTING_FILE_DIR, GOLD_CCT_DIRNAME)
|
||||
export_dir = os.path.join(TESTING_FILE_DIR, "test_evaluate_results_cct")
|
||||
|
||||
TextExtractionMetricsCalculator(
|
||||
documents_dir=output_dir,
|
||||
ground_truths_dir=source_dir,
|
||||
).on_files(document_paths=output_list).calculate(export_dir=export_dir)
|
||||
|
||||
# check that only the listed files are included
|
||||
assert os.path.isfile(os.path.join(export_dir, "all-docs-cct.tsv"))
|
||||
df = pd.read_csv(os.path.join(export_dir, "all-docs-cct.tsv"), sep="\t")
|
||||
assert len(df) == len(output_list)
|
||||
|
||||
|
||||
@pytest.mark.skipif(is_in_docker, reason="Skipping this test in Docker container")
|
||||
@pytest.mark.usefixtures("_cleanup_after_test")
|
||||
def test_text_extraction_with_grouping():
|
||||
output_dir = os.path.join(TESTING_FILE_DIR, UNSTRUCTURED_OUTPUT_DIRNAME)
|
||||
source_dir = os.path.join(TESTING_FILE_DIR, GOLD_CCT_DIRNAME)
|
||||
export_dir = os.path.join(TESTING_FILE_DIR, "test_evaluate_results_cct")
|
||||
|
||||
TextExtractionMetricsCalculator(
|
||||
documents_dir=output_dir,
|
||||
ground_truths_dir=source_dir,
|
||||
group_by="doctype",
|
||||
).calculate(export_dir=export_dir)
|
||||
|
||||
df = pd.read_csv(os.path.join(export_dir, "all-doctype-agg-cct.tsv"), sep="\t")
|
||||
assert len(df) == 4 # metrics row and doctype rows
|
||||
|
||||
|
||||
@pytest.mark.skipif(is_in_docker, reason="Skipping this test in Docker container")
|
||||
@pytest.mark.usefixtures("_cleanup_after_test")
|
||||
def test_text_extraction_wrong_type():
|
||||
output_dir = os.path.join(TESTING_FILE_DIR, UNSTRUCTURED_OUTPUT_DIRNAME)
|
||||
source_dir = os.path.join(TESTING_FILE_DIR, GOLD_CCT_DIRNAME)
|
||||
export_dir = os.path.join(TESTING_FILE_DIR, "test_evaluate_results_cct")
|
||||
with pytest.raises(ValueError):
|
||||
TextExtractionMetricsCalculator(
|
||||
documents_dir=output_dir, ground_truths_dir=source_dir, document_type="invalid type"
|
||||
).calculate(export_dir=export_dir)
|
||||
|
||||
|
||||
@pytest.mark.skipif(is_in_docker, reason="Skipping this test in Docker container")
|
||||
@pytest.mark.usefixtures("_cleanup_after_test")
|
||||
@pytest.mark.parametrize(("grouping", "count_row"), [("doctype", 3), ("connector", 2)])
|
||||
def test_get_mean_grouping_df_input(grouping: str, count_row: int):
|
||||
export_dir = os.path.join(TESTING_FILE_DIR, "test_evaluate_results_cct")
|
||||
get_mean_grouping(
|
||||
group_by=grouping,
|
||||
data_input=DUMMY_DF_CCT,
|
||||
export_dir=export_dir,
|
||||
eval_name="text_extraction",
|
||||
)
|
||||
grouped_df = pd.read_csv(os.path.join(export_dir, f"all-{grouping}-agg-cct.tsv"), sep="\t")
|
||||
assert grouped_df[grouping].dropna().nunique() == count_row
|
||||
|
||||
|
||||
@pytest.mark.skipif(is_in_docker, reason="Skipping this test in Docker container")
|
||||
@pytest.mark.usefixtures("_cleanup_after_test")
|
||||
def test_get_mean_grouping_tsv_input():
|
||||
output_dir = os.path.join(TESTING_FILE_DIR, UNSTRUCTURED_OUTPUT_DIRNAME)
|
||||
source_dir = os.path.join(TESTING_FILE_DIR, GOLD_CCT_DIRNAME)
|
||||
export_dir = os.path.join(TESTING_FILE_DIR, "test_evaluate_results_cct")
|
||||
|
||||
TextExtractionMetricsCalculator(
|
||||
documents_dir=output_dir,
|
||||
ground_truths_dir=source_dir,
|
||||
).calculate(export_dir=export_dir)
|
||||
|
||||
filename = os.path.join(export_dir, "all-docs-cct.tsv")
|
||||
get_mean_grouping(
|
||||
group_by="doctype",
|
||||
data_input=filename,
|
||||
export_dir=export_dir,
|
||||
eval_name="text_extraction",
|
||||
)
|
||||
grouped_df = pd.read_csv(os.path.join(export_dir, "all-doctype-agg-cct.tsv"), sep="\t")
|
||||
assert grouped_df["doctype"].dropna().nunique() == 3
|
||||
|
||||
|
||||
@pytest.mark.skipif(is_in_docker, reason="Skipping this test in Docker container")
|
||||
@pytest.mark.usefixtures("_cleanup_after_test")
|
||||
def test_get_mean_grouping_invalid_group():
|
||||
output_dir = os.path.join(TESTING_FILE_DIR, UNSTRUCTURED_OUTPUT_DIRNAME)
|
||||
source_dir = os.path.join(TESTING_FILE_DIR, GOLD_CCT_DIRNAME)
|
||||
export_dir = os.path.join(TESTING_FILE_DIR, "test_evaluate_results_cct")
|
||||
|
||||
TextExtractionMetricsCalculator(
|
||||
documents_dir=output_dir,
|
||||
ground_truths_dir=source_dir,
|
||||
).calculate(export_dir=export_dir)
|
||||
|
||||
df = pd.read_csv(os.path.join(export_dir, "all-docs-cct.tsv"), sep="\t")
|
||||
with pytest.raises(ValueError):
|
||||
get_mean_grouping(
|
||||
group_by="invalid",
|
||||
data_input=df,
|
||||
export_dir=export_dir,
|
||||
eval_name="text_extraction",
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.skipif(is_in_docker, reason="Skipping this test in Docker container")
|
||||
@pytest.mark.usefixtures("_cleanup_after_test")
|
||||
def test_text_extraction_grouping_empty_df():
|
||||
empty_df = pd.DataFrame()
|
||||
with pytest.raises(SystemExit):
|
||||
get_mean_grouping("doctype", empty_df, "some_dir", eval_name="text_extraction")
|
||||
|
||||
|
||||
@pytest.mark.skipif(is_in_docker, reason="Skipping this test in Docker container")
|
||||
@pytest.mark.usefixtures("_cleanup_after_test")
|
||||
def test_get_mean_grouping_missing_grouping_column():
|
||||
df_with_no_grouping = pd.DataFrame({"some_column": [1, 2, 3]})
|
||||
with pytest.raises(SystemExit):
|
||||
get_mean_grouping("doctype", df_with_no_grouping, "some_dir", "text_extraction")
|
||||
|
||||
|
||||
@pytest.mark.skipif(is_in_docker, reason="Skipping this test in Docker container")
|
||||
@pytest.mark.usefixtures("_cleanup_after_test")
|
||||
def test_get_mean_grouping_all_null_grouping_column():
|
||||
df_with_null_grouping = pd.DataFrame({"doctype": [None, None, None]})
|
||||
with pytest.raises(SystemExit):
|
||||
get_mean_grouping("doctype", df_with_null_grouping, "some_dir", eval_name="text_extraction")
|
||||
|
||||
|
||||
@pytest.mark.skipif(is_in_docker, reason="Skipping this test in Docker container")
|
||||
@pytest.mark.usefixtures("_cleanup_after_test")
|
||||
def test_get_mean_grouping_invalid_eval_name():
|
||||
with pytest.raises(ValueError):
|
||||
get_mean_grouping("doctype", DUMMY_DF_ELEMENT_TYPE, "some_dir", eval_name="invalid")
|
||||
|
||||
|
||||
@pytest.mark.skipif(is_in_docker, reason="Skipping this test in Docker container")
|
||||
@pytest.mark.usefixtures("_cleanup_after_test")
|
||||
@pytest.mark.parametrize(("group_by", "count_row"), [("doctype", 3), ("connector", 2)])
|
||||
def test_get_mean_grouping_element_type(group_by: str, count_row: int):
|
||||
export_dir = os.path.join(TESTING_FILE_DIR, "test_evaluate_results_element_type")
|
||||
get_mean_grouping(
|
||||
group_by=group_by,
|
||||
data_input=DUMMY_DF_ELEMENT_TYPE,
|
||||
export_dir=export_dir,
|
||||
eval_name="element_type",
|
||||
)
|
||||
grouped_df = pd.read_csv(
|
||||
os.path.join(export_dir, f"all-{group_by}-agg-element-type.tsv"), sep="\t"
|
||||
)
|
||||
assert grouped_df[group_by].dropna().nunique() == count_row
|
||||
|
||||
|
||||
@pytest.mark.skipif(is_in_docker, reason="Skipping this test in Docker container")
|
||||
@pytest.mark.usefixtures("_cleanup_after_test")
|
||||
def test_filter_metrics():
|
||||
with open(os.path.join(TESTING_FILE_DIR, "filter_list.txt"), "w") as file:
|
||||
file.write("Bank Good Credit Loan.pptx\n")
|
||||
file.write("Performance-Audit-Discussion.pdf\n")
|
||||
export_dir = os.path.join(TESTING_FILE_DIR, "test_evaluate_results_cct")
|
||||
|
||||
filter_metrics(
|
||||
data_input=DUMMY_DF_CCT,
|
||||
filter_list=os.path.join(TESTING_FILE_DIR, "filter_list.txt"),
|
||||
filter_by="filename",
|
||||
export_filename="filtered_metrics.tsv",
|
||||
export_dir=export_dir,
|
||||
return_type="file",
|
||||
)
|
||||
filtered_df = pd.read_csv(os.path.join(export_dir, "filtered_metrics.tsv"), sep="\t")
|
||||
assert len(filtered_df) == 2
|
||||
assert filtered_df["filename"].iloc[0] == "Bank Good Credit Loan.pptx"
|
||||
|
||||
|
||||
@pytest.mark.skipif(is_in_docker, reason="Skipping this test in Docker container")
|
||||
@pytest.mark.usefixtures("_cleanup_after_test")
|
||||
def test_get_mean_grouping_all_file():
|
||||
with open(os.path.join(TESTING_FILE_DIR, "filter_list.txt"), "w") as file:
|
||||
file.write("Bank Good Credit Loan.pptx\n")
|
||||
file.write("Performance-Audit-Discussion.pdf\n")
|
||||
export_dir = os.path.join(TESTING_FILE_DIR, "test_evaluate_results_cct")
|
||||
|
||||
filter_metrics(
|
||||
data_input=DUMMY_DF_CCT,
|
||||
filter_list=["Bank Good Credit Loan.pptx", "Performance-Audit-Discussion.pdf"],
|
||||
filter_by="filename",
|
||||
export_filename="filtered_metrics.tsv",
|
||||
export_dir=export_dir,
|
||||
return_type="file",
|
||||
)
|
||||
filtered_df = pd.read_csv(os.path.join(export_dir, "filtered_metrics.tsv"), sep="\t")
|
||||
|
||||
get_mean_grouping(
|
||||
group_by="all",
|
||||
data_input=filtered_df,
|
||||
export_dir=export_dir,
|
||||
eval_name="text_extraction",
|
||||
export_filename="two-filename-agg-cct.tsv",
|
||||
)
|
||||
grouped_df = pd.read_csv(os.path.join(export_dir, "two-filename-agg-cct.tsv"), sep="\t")
|
||||
|
||||
assert np.isclose(float(grouped_df.iloc[1, 0]), 0.903)
|
||||
assert np.isclose(float(grouped_df.iloc[1, 1]), 0.129)
|
||||
assert np.isclose(float(grouped_df.iloc[1, 2]), 0.091)
|
||||
|
||||
|
||||
@pytest.mark.skipif(is_in_docker, reason="Skipping this test in Docker container")
|
||||
@pytest.mark.usefixtures("_cleanup_after_test")
|
||||
def test_get_mean_grouping_all_file_txt():
|
||||
with open(os.path.join(TESTING_FILE_DIR, "filter_list.txt"), "w") as file:
|
||||
file.write("Bank Good Credit Loan.pptx\n")
|
||||
file.write("Performance-Audit-Discussion.pdf\n")
|
||||
export_dir = os.path.join(TESTING_FILE_DIR, "test_evaluate_results_cct")
|
||||
|
||||
filter_metrics(
|
||||
data_input=DUMMY_DF_CCT,
|
||||
filter_list=os.path.join(TESTING_FILE_DIR, "filter_list.txt"),
|
||||
filter_by="filename",
|
||||
export_filename="filtered_metrics.tsv",
|
||||
export_dir=export_dir,
|
||||
return_type="file",
|
||||
)
|
||||
filtered_df = pd.read_csv(os.path.join(export_dir, "filtered_metrics.tsv"), sep="\t")
|
||||
|
||||
get_mean_grouping(
|
||||
group_by="all",
|
||||
data_input=filtered_df,
|
||||
export_dir=export_dir,
|
||||
eval_name="text_extraction",
|
||||
export_filename="two-filename-agg-cct.tsv",
|
||||
)
|
||||
grouped_df = pd.read_csv(os.path.join(export_dir, "two-filename-agg-cct.tsv"), sep="\t")
|
||||
|
||||
assert np.isclose(float(grouped_df.iloc[1, 0]), 0.903)
|
||||
assert np.isclose(float(grouped_df.iloc[1, 1]), 0.129)
|
||||
assert np.isclose(float(grouped_df.iloc[1, 2]), 0.091)
|
||||
@@ -0,0 +1,14 @@
|
||||
from unstructured.metrics.table.table_alignment import TableAlignment
|
||||
|
||||
|
||||
def test_get_element_level_alignment_when_no_match():
|
||||
example_table = [{"row_index": 0, "col_index": 0, "content": "a"}]
|
||||
metrics = TableAlignment.get_element_level_alignment(
|
||||
predicted_table_data=[example_table],
|
||||
ground_truth_table_data=[example_table],
|
||||
matched_indices=[-1],
|
||||
)
|
||||
assert metrics["col_index_acc"] == 0
|
||||
assert metrics["row_index_acc"] == 0
|
||||
assert metrics["row_content_acc"] == 0
|
||||
assert metrics["col_content_acc"] == 0
|
||||
@@ -0,0 +1,33 @@
|
||||
import pytest
|
||||
|
||||
from unstructured.metrics.table.table_eval import calculate_table_detection_metrics
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("matched_indices", "ground_truth_tables_number", "expected_metrics"),
|
||||
[
|
||||
([0, 1, 2], 3, (1, 1, 1)), # everything was predicted correctly
|
||||
([2, 1, 0], 3, (1, 1, 1)), # everything was predicted correctly
|
||||
(
|
||||
[-1, 2, -1, 1, 0, -1],
|
||||
3,
|
||||
(1, 0.5, 0.66),
|
||||
), # some false positives, all tables matched, too many predictions
|
||||
([2, 2, 1, 1], 8, (0.25, 0.5, 0.33)),
|
||||
# Some false negatives, all predictions matched with gt, not enough predictions
|
||||
# The precision here is not 1 as only one from tables matched with '1' index can be correct
|
||||
([1, -1], 2, (0.5, 0.5, 0.5)), # typical case with false positive and false negative
|
||||
([-1, -1, -1], 2, (0, 0, 0)), # nothing was matched
|
||||
([-1, -1, -1], 0, (0, 0, 0)), # there was no table in ground truth
|
||||
([], 0, (0, 0, 0)), # just zeros to account for errors
|
||||
],
|
||||
)
|
||||
def test_calculate_table_metrics(matched_indices, ground_truth_tables_number, expected_metrics):
|
||||
expected_recall, expected_precision, expected_f1 = expected_metrics
|
||||
pred_recall, pred_precision, pred_f1 = calculate_table_detection_metrics(
|
||||
matched_indices=matched_indices, ground_truth_tables_number=ground_truth_tables_number
|
||||
)
|
||||
|
||||
assert pred_recall == expected_recall
|
||||
assert pred_precision == expected_precision
|
||||
assert pred_f1 == pytest.approx(expected_f1, abs=0.01)
|
||||
@@ -0,0 +1,33 @@
|
||||
import pytest
|
||||
|
||||
from unstructured.metrics.table.table_formats import SimpleTableCell
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("row_nums", "column_nums", "x", "y", "w", "h"),
|
||||
[
|
||||
([3, 2, 1], [6, 7], 6, 1, 2, 3),
|
||||
([2], [6, 7], 6, 2, 2, 1),
|
||||
([1, 2, 3], [20], 20, 1, 1, 3),
|
||||
([5], [5], 5, 5, 1, 1),
|
||||
],
|
||||
)
|
||||
def test_simple_table_cell_parsing_from_table_transformer_when_expected_input(
|
||||
row_nums, column_nums, x, y, w, h
|
||||
):
|
||||
table_transformer_cell = {"row_nums": row_nums, "column_nums": column_nums, "cell text": "text"}
|
||||
transformed_cell = SimpleTableCell.from_table_transformer_cell(table_transformer_cell)
|
||||
expected_cell = SimpleTableCell(x=x, y=y, w=w, h=h, content="text")
|
||||
assert expected_cell == transformed_cell
|
||||
|
||||
|
||||
def test_simple_table_cell_parsing_from_table_transformer_when_missing_row_nums():
|
||||
cell = {"row_nums": [], "column_nums": [1], "cell text": "text"}
|
||||
with pytest.raises(ValueError, match='has missing values under "row_nums" key'):
|
||||
SimpleTableCell.from_table_transformer_cell(cell)
|
||||
|
||||
|
||||
def test_simple_table_cell_parsing_from_table_transformer_when_missing_column_nums():
|
||||
cell = {"row_nums": [1], "column_nums": [], "cell text": "text"}
|
||||
with pytest.raises(ValueError, match='has missing values under "column_nums" key'):
|
||||
SimpleTableCell.from_table_transformer_cell(cell)
|
||||
@@ -0,0 +1,700 @@
|
||||
from unittest import mock
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from test_unstructured.unit_utils import example_doc_path
|
||||
from unstructured.metrics.table.table_alignment import TableAlignment
|
||||
from unstructured.metrics.table.table_eval import TableEvalProcessor
|
||||
from unstructured.metrics.table_structure import (
|
||||
eval_table_transformer_for_file,
|
||||
image_or_pdf_to_dataframe,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"filename",
|
||||
[
|
||||
example_doc_path("img/table-multi-row-column-cells.png"),
|
||||
example_doc_path("pdf/table-multi-row-column-cells.pdf"),
|
||||
],
|
||||
)
|
||||
def test_image_or_pdf_to_dataframe(filename):
|
||||
df = image_or_pdf_to_dataframe(filename)
|
||||
assert ["Blind", "5", "1", "4", "34.5%, n=1", "1199 sec, n=1"] in df.values
|
||||
|
||||
|
||||
def test_eval_table_transformer_for_file():
|
||||
score = eval_table_transformer_for_file(
|
||||
example_doc_path("img/table-multi-row-column-cells.png"),
|
||||
example_doc_path("table-multi-row-column-cells-actual.csv"),
|
||||
)
|
||||
# avoid severe degradation of performance
|
||||
assert 0.8 < score < 1
|
||||
|
||||
|
||||
def test_table_eval_processor_simple():
|
||||
prediction = [
|
||||
{
|
||||
"type": "Table",
|
||||
"metadata": {
|
||||
"text_as_html": """<table><thead><tr><th>r1c1</th><th>r1c2</th></tr></thead>
|
||||
<tbody><tr><td>r2c1</td><td>r2c2</td></tr></tbody></table>"""
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
ground_truth = [
|
||||
{
|
||||
"type": "Table",
|
||||
"text": [
|
||||
{
|
||||
"id": "ee862c7a-d27e-4484-92de-4faa42a63f3b",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 1,
|
||||
"h": 1,
|
||||
"content": "r1c1",
|
||||
},
|
||||
{
|
||||
"id": "6237ac7b-bfc8-40d2-92f2-d138277205e2",
|
||||
"x": 0,
|
||||
"y": 1,
|
||||
"w": 1,
|
||||
"h": 1,
|
||||
"content": "r2c1",
|
||||
},
|
||||
{
|
||||
"id": "9d0933a9-5984-4cad-80d9-6752bf9bc4df",
|
||||
"x": 1,
|
||||
"y": 0,
|
||||
"w": 1,
|
||||
"h": 1,
|
||||
"content": "r1c2",
|
||||
},
|
||||
{
|
||||
"id": "1152d043-5ead-4ab8-8b88-888d48831ac2",
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"w": 1,
|
||||
"h": 1,
|
||||
"content": "r2c2",
|
||||
},
|
||||
],
|
||||
}
|
||||
]
|
||||
|
||||
te_processor = TableEvalProcessor(prediction, ground_truth)
|
||||
result = te_processor.process_file()
|
||||
assert result.total_tables == 1
|
||||
assert result.table_level_acc == 1.0
|
||||
assert result.element_row_level_index_acc == 1.0
|
||||
assert result.element_col_level_index_acc == 1.0
|
||||
assert result.element_row_level_content_acc == 1.0
|
||||
assert result.element_col_level_content_acc == 1.0
|
||||
|
||||
|
||||
def test_table_eval_processor_simple_when_input_as_cells():
|
||||
prediction = [
|
||||
{
|
||||
"type": "Table",
|
||||
"metadata": {
|
||||
"table_as_cells": [
|
||||
{
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"w": 1,
|
||||
"h": 1,
|
||||
"content": "r2c2",
|
||||
},
|
||||
{
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 1,
|
||||
"h": 1,
|
||||
"content": "r1c1",
|
||||
},
|
||||
{
|
||||
"x": 0,
|
||||
"y": 1,
|
||||
"w": 1,
|
||||
"h": 1,
|
||||
"content": "r2c1",
|
||||
},
|
||||
{
|
||||
"x": 1,
|
||||
"y": 0,
|
||||
"w": 1,
|
||||
"h": 1,
|
||||
"content": "r1c2",
|
||||
},
|
||||
]
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
ground_truth = [
|
||||
{
|
||||
"type": "Table",
|
||||
"text": [
|
||||
{
|
||||
"id": "ee862c7a-d27e-4484-92de-4faa42a63f3b",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 1,
|
||||
"h": 1,
|
||||
"content": "r1c1",
|
||||
},
|
||||
{
|
||||
"id": "6237ac7b-bfc8-40d2-92f2-d138277205e2",
|
||||
"x": 0,
|
||||
"y": 1,
|
||||
"w": 1,
|
||||
"h": 1,
|
||||
"content": "r2c1",
|
||||
},
|
||||
{
|
||||
"id": "9d0933a9-5984-4cad-80d9-6752bf9bc4df",
|
||||
"x": 1,
|
||||
"y": 0,
|
||||
"w": 1,
|
||||
"h": 1,
|
||||
"content": "r1c2",
|
||||
},
|
||||
{
|
||||
"id": "1152d043-5ead-4ab8-8b88-888d48831ac2",
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"w": 1,
|
||||
"h": 1,
|
||||
"content": "r2c2",
|
||||
},
|
||||
],
|
||||
}
|
||||
]
|
||||
|
||||
te_processor = TableEvalProcessor(prediction, ground_truth, source_type="cells")
|
||||
result = te_processor.process_file()
|
||||
assert result.total_tables == 1
|
||||
assert result.table_level_acc == 1.0
|
||||
assert result.element_row_level_index_acc == 1.0
|
||||
assert result.element_col_level_index_acc == 1.0
|
||||
assert result.element_row_level_content_acc == 1.0
|
||||
assert result.element_col_level_content_acc == 1.0
|
||||
|
||||
|
||||
def test_table_eval_processor_when_wrong_source_type():
|
||||
prediction = [
|
||||
{
|
||||
"type": "Table",
|
||||
"metadata": {"table_as_cells": []},
|
||||
}
|
||||
]
|
||||
|
||||
ground_truth = [
|
||||
{
|
||||
"type": "Table",
|
||||
"text": [],
|
||||
}
|
||||
]
|
||||
|
||||
te_processor = TableEvalProcessor(prediction, ground_truth, source_type="wrong_type")
|
||||
with pytest.raises(ValueError):
|
||||
te_processor.process_file()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"text_as_html",
|
||||
[
|
||||
"""
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>r1c1</th>
|
||||
<th>r1c2</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>r2c1</td>
|
||||
<td>r2c2</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>r3c1</td>
|
||||
<td>r3c2</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
""",
|
||||
"""
|
||||
<table>
|
||||
<tr>
|
||||
<th>r1c1</th>
|
||||
<th>r1c2</th>
|
||||
</tr>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>r2c1</td>
|
||||
<td>r2c2</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>r3c1</td>
|
||||
<td>r3c2</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
""",
|
||||
"""
|
||||
<table>
|
||||
</tbody>
|
||||
<tr>
|
||||
<td>r1c1</td>
|
||||
<td>r1c2</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>r2c1</td>
|
||||
<td>r2c2</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>r3c1</td>
|
||||
<td>r3c2</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
""",
|
||||
],
|
||||
)
|
||||
def test_table_eval_processor_various_table_html_structures(text_as_html):
|
||||
prediction = [{"type": "Table", "metadata": {"text_as_html": text_as_html}}]
|
||||
|
||||
ground_truth = [
|
||||
{
|
||||
"type": "Table",
|
||||
"text": [
|
||||
{
|
||||
"id": "ee862c7a-d27e-4484-92de-4faa42a63f3b",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 1,
|
||||
"h": 1,
|
||||
"content": "r1c1",
|
||||
},
|
||||
{
|
||||
"id": "6237ac7b-bfc8-40d2-92f2-d138277205e2",
|
||||
"x": 0,
|
||||
"y": 1,
|
||||
"w": 1,
|
||||
"h": 1,
|
||||
"content": "r2c1",
|
||||
},
|
||||
{
|
||||
"id": "9d0933a9-5984-4cad-80d9-6752bf9bc4df",
|
||||
"x": 1,
|
||||
"y": 0,
|
||||
"w": 1,
|
||||
"h": 1,
|
||||
"content": "r1c2",
|
||||
},
|
||||
{
|
||||
"id": "1152d043-5ead-4ab8-8b88-888d48831ac2",
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"w": 1,
|
||||
"h": 1,
|
||||
"content": "r2c2",
|
||||
},
|
||||
{
|
||||
"id": "364f4a17-2979-4506-ae77-e8adf8e3f554",
|
||||
"x": 0,
|
||||
"y": 2,
|
||||
"w": 1,
|
||||
"h": 1,
|
||||
"content": "r3c1",
|
||||
},
|
||||
{
|
||||
"id": "30f87503-ac1f-4db1-b924-b316af585702",
|
||||
"x": 1,
|
||||
"y": 2,
|
||||
"w": 1,
|
||||
"h": 1,
|
||||
"content": "r3c2",
|
||||
},
|
||||
],
|
||||
}
|
||||
]
|
||||
|
||||
te_processor = TableEvalProcessor(prediction, ground_truth)
|
||||
result = te_processor.process_file()
|
||||
assert result.total_tables == 1
|
||||
assert result.table_level_acc == 1.0
|
||||
assert result.element_row_level_index_acc == 1.0
|
||||
assert result.element_col_level_index_acc == 1.0
|
||||
assert result.element_row_level_content_acc == 1.0
|
||||
assert result.element_col_level_content_acc == 1.0
|
||||
|
||||
|
||||
def test_table_eval_processor_non_str_values_in_table():
|
||||
prediction = [
|
||||
{
|
||||
"type": "Table",
|
||||
"metadata": {
|
||||
"text_as_html": """
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>11</th>
|
||||
<th>12</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>21</td>
|
||||
<td>22</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>"""
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
ground_truth = [
|
||||
{
|
||||
"type": "Table",
|
||||
"text": [
|
||||
{
|
||||
"id": "ee862c7a-d27e-4484-92de-4faa42a63f3b",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 1,
|
||||
"h": 1,
|
||||
"content": "11",
|
||||
},
|
||||
{
|
||||
"id": "6237ac7b-bfc8-40d2-92f2-d138277205e2",
|
||||
"x": 0,
|
||||
"y": 1,
|
||||
"w": 1,
|
||||
"h": 1,
|
||||
"content": "21",
|
||||
},
|
||||
{
|
||||
"id": "9d0933a9-5984-4cad-80d9-6752bf9bc4df",
|
||||
"x": 1,
|
||||
"y": 0,
|
||||
"w": 1,
|
||||
"h": 1,
|
||||
"content": "12",
|
||||
},
|
||||
{
|
||||
"id": "1152d043-5ead-4ab8-8b88-888d48831ac2",
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"w": 1,
|
||||
"h": 1,
|
||||
"content": "22",
|
||||
},
|
||||
],
|
||||
}
|
||||
]
|
||||
|
||||
te_processor = TableEvalProcessor(prediction, ground_truth)
|
||||
result = te_processor.process_file()
|
||||
assert result.total_tables == 1
|
||||
assert result.table_level_acc == 1.0
|
||||
assert result.element_row_level_index_acc == 1.0
|
||||
assert result.element_col_level_index_acc == 1.0
|
||||
assert result.element_row_level_content_acc == 1.0
|
||||
assert result.element_col_level_content_acc == 1.0
|
||||
|
||||
|
||||
def test_table_eval_processor_merged_cells():
|
||||
prediction = [
|
||||
{
|
||||
"type": "Table",
|
||||
"metadata": {
|
||||
"text_as_html": """
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th rowspan="2">r1c1</th>
|
||||
<th>r1c2</th>
|
||||
<th colspan="2">r1c3</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>r2c2</th>
|
||||
<th>r2c3</th>
|
||||
<th>r2c4</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>r3c1</td>
|
||||
<td>r3c2</td>
|
||||
<td colspan="2" rowspan="2">r3c3</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>r4c1</td>
|
||||
<td>r4c2</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
"""
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
ground_truth = [
|
||||
{
|
||||
"type": "Table",
|
||||
"text": [
|
||||
{
|
||||
"id": "f399ef57-5b88-4509-8971-9cb63246866e",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 1,
|
||||
"h": 2,
|
||||
"content": "r1c1",
|
||||
},
|
||||
{
|
||||
"id": "2dfdec2f-e8f3-4be7-a6ac-8ff21c4e8556",
|
||||
"x": 0,
|
||||
"y": 2,
|
||||
"w": 1,
|
||||
"h": 1,
|
||||
"content": "r3c1",
|
||||
},
|
||||
{
|
||||
"id": "9c771c58-88c7-49d8-9c12-85d0e44b920e",
|
||||
"x": 0,
|
||||
"y": 3,
|
||||
"w": 1,
|
||||
"h": 1,
|
||||
"content": "r4c1",
|
||||
},
|
||||
{
|
||||
"id": "5bd6f3f0-34c5-495b-8a28-c4ac96989ef8",
|
||||
"x": 1,
|
||||
"y": 0,
|
||||
"w": 1,
|
||||
"h": 1,
|
||||
"content": "r1c2",
|
||||
},
|
||||
{
|
||||
"id": "7b8e6bc2-a310-4dd6-997c-313f951e7f96",
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"w": 1,
|
||||
"h": 1,
|
||||
"content": "r2c2",
|
||||
},
|
||||
{
|
||||
"id": "1c152ad4-12fa-4a7b-90de-a992aa6410a4",
|
||||
"x": 1,
|
||||
"y": 2,
|
||||
"w": 1,
|
||||
"h": 1,
|
||||
"content": "r3c2",
|
||||
},
|
||||
{
|
||||
"id": "55063f64-0003-4217-b6ca-aff5914793ff",
|
||||
"x": 1,
|
||||
"y": 3,
|
||||
"w": 1,
|
||||
"h": 1,
|
||||
"content": "r4c2",
|
||||
},
|
||||
{
|
||||
"id": "22852e86-0e22-4d32-b63a-9ba7dd4118a2",
|
||||
"x": 2,
|
||||
"y": 0,
|
||||
"w": 2,
|
||||
"h": 1,
|
||||
"content": "r1c3",
|
||||
},
|
||||
{
|
||||
"id": "eae013c5-5597-4a8b-9771-82e28c5c5cba",
|
||||
"x": 2,
|
||||
"y": 1,
|
||||
"w": 1,
|
||||
"h": 1,
|
||||
"content": "r2c3",
|
||||
},
|
||||
{
|
||||
"id": "0dea3a42-8523-4d6e-9e70-d65cc2314678",
|
||||
"x": 2,
|
||||
"y": 2,
|
||||
"w": 2,
|
||||
"h": 2,
|
||||
"content": "r3c3",
|
||||
},
|
||||
{
|
||||
"id": "60093e2c-d3e2-4146-92b5-97a2fc16c061",
|
||||
"x": 3,
|
||||
"y": 1,
|
||||
"w": 1,
|
||||
"h": 1,
|
||||
"content": "r2c4",
|
||||
},
|
||||
],
|
||||
}
|
||||
]
|
||||
|
||||
te_processor = TableEvalProcessor(prediction, ground_truth)
|
||||
result = te_processor.process_file()
|
||||
assert result.total_tables == 1
|
||||
assert result.table_level_acc == 1.0
|
||||
assert result.element_row_level_index_acc == 1.0
|
||||
assert result.element_col_level_index_acc == 1.0
|
||||
assert result.element_row_level_content_acc == 1.0
|
||||
assert result.element_col_level_content_acc == 1.0
|
||||
|
||||
|
||||
def test_table_eval_processor_when_no_match_with_pred():
|
||||
prediction = [
|
||||
{
|
||||
"type": "Table",
|
||||
"metadata": {"text_as_html": """<table><tr><td>Some cell</td></tr></table>"""},
|
||||
}
|
||||
]
|
||||
|
||||
ground_truth = [
|
||||
{
|
||||
"type": "Table",
|
||||
"text": [
|
||||
{
|
||||
"id": "ee862c7a-d27e-4484-92de-4faa42a63f3b",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 1,
|
||||
"h": 1,
|
||||
"content": "11",
|
||||
},
|
||||
{
|
||||
"id": "6237ac7b-bfc8-40d2-92f2-d138277205e2",
|
||||
"x": 0,
|
||||
"y": 1,
|
||||
"w": 1,
|
||||
"h": 1,
|
||||
"content": "21",
|
||||
},
|
||||
{
|
||||
"id": "9d0933a9-5984-4cad-80d9-6752bf9bc4df",
|
||||
"x": 1,
|
||||
"y": 0,
|
||||
"w": 1,
|
||||
"h": 1,
|
||||
"content": "12",
|
||||
},
|
||||
{
|
||||
"id": "1152d043-5ead-4ab8-8b88-888d48831ac2",
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"w": 1,
|
||||
"h": 1,
|
||||
"content": "22",
|
||||
},
|
||||
],
|
||||
}
|
||||
]
|
||||
|
||||
with mock.patch.object(TableAlignment, "get_table_level_alignment") as align_fn:
|
||||
align_fn.return_value = [-1]
|
||||
te_processor = TableEvalProcessor(prediction, ground_truth)
|
||||
result = te_processor.process_file()
|
||||
|
||||
assert result.total_tables == 1
|
||||
assert result.table_level_acc == 0
|
||||
assert result.element_row_level_index_acc == 0
|
||||
assert result.element_col_level_index_acc == 0
|
||||
assert result.element_row_level_content_acc == 0
|
||||
assert result.element_col_level_content_acc == 0
|
||||
|
||||
|
||||
def test_table_eval_processor_when_no_tables():
|
||||
prediction = [{}]
|
||||
|
||||
ground_truth = [{}]
|
||||
|
||||
te_processor = TableEvalProcessor(prediction, ground_truth)
|
||||
result = te_processor.process_file()
|
||||
assert result.total_tables == 0
|
||||
assert result.table_level_acc == 1
|
||||
assert np.isnan(result.element_row_level_index_acc)
|
||||
assert np.isnan(result.element_col_level_index_acc)
|
||||
assert np.isnan(result.element_row_level_content_acc)
|
||||
assert np.isnan(result.element_col_level_content_acc)
|
||||
|
||||
|
||||
def test_table_eval_processor_when_only_gt():
|
||||
prediction = []
|
||||
|
||||
ground_truth = [
|
||||
{
|
||||
"type": "Table",
|
||||
"text": [
|
||||
{
|
||||
"id": "ee862c7a-d27e-4484-92de-4faa42a63f3b",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 1,
|
||||
"h": 1,
|
||||
"content": "11",
|
||||
},
|
||||
{
|
||||
"id": "6237ac7b-bfc8-40d2-92f2-d138277205e2",
|
||||
"x": 0,
|
||||
"y": 1,
|
||||
"w": 1,
|
||||
"h": 1,
|
||||
"content": "21",
|
||||
},
|
||||
{
|
||||
"id": "9d0933a9-5984-4cad-80d9-6752bf9bc4df",
|
||||
"x": 1,
|
||||
"y": 0,
|
||||
"w": 1,
|
||||
"h": 1,
|
||||
"content": "12",
|
||||
},
|
||||
{
|
||||
"id": "1152d043-5ead-4ab8-8b88-888d48831ac2",
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"w": 1,
|
||||
"h": 1,
|
||||
"content": "22",
|
||||
},
|
||||
],
|
||||
}
|
||||
]
|
||||
|
||||
te_processor = TableEvalProcessor(prediction, ground_truth)
|
||||
result = te_processor.process_file()
|
||||
|
||||
assert result.total_tables == 1
|
||||
assert result.table_level_acc == 0
|
||||
assert result.element_row_level_index_acc == 0
|
||||
assert result.element_col_level_index_acc == 0
|
||||
assert result.element_row_level_content_acc == 0
|
||||
assert result.element_col_level_content_acc == 0
|
||||
|
||||
|
||||
def test_table_eval_processor_when_only_pred():
|
||||
prediction = [
|
||||
{
|
||||
"type": "Table",
|
||||
"metadata": {"text_as_html": """<table><tr><td>Some cell</td></tr></table>"""},
|
||||
}
|
||||
]
|
||||
|
||||
ground_truth = [{}]
|
||||
|
||||
te_processor = TableEvalProcessor(prediction, ground_truth)
|
||||
result = te_processor.process_file()
|
||||
|
||||
assert result.total_tables == 0
|
||||
assert result.table_level_acc == 0
|
||||
assert result.element_row_level_index_acc == 0
|
||||
assert result.element_col_level_index_acc == 0
|
||||
assert result.element_row_level_content_acc == 0
|
||||
assert result.element_col_level_content_acc == 0
|
||||
@@ -0,0 +1,859 @@
|
||||
import re
|
||||
|
||||
import pytest
|
||||
|
||||
from unstructured.metrics import text_extraction
|
||||
from unstructured.metrics.table.table_extraction import (
|
||||
deckerd_table_to_html,
|
||||
extract_cells_from_table_as_cells,
|
||||
extract_cells_from_text_as_html,
|
||||
html_table_to_deckerd,
|
||||
)
|
||||
from unstructured.partition.auto import partition
|
||||
|
||||
|
||||
def test_calculate_edit_distance():
|
||||
source_cct = "I like pizza. I like bagels."
|
||||
source_cct_word_space = "I like p i z z a . I like bagles."
|
||||
source_cct_spaces = re.sub(r"\s+", " ", " ".join(source_cct))
|
||||
source_cct_no_space = source_cct.replace(" ", "")
|
||||
source_cct_one_sentence = "I like pizza."
|
||||
source_cct_missing_word = "I like pizza. I like ."
|
||||
source_cct_addn_char = "I like pizza. I like beagles."
|
||||
source_cct_dup_word = "I like pizza pizza. I like bagels."
|
||||
|
||||
assert (
|
||||
round(text_extraction.calculate_edit_distance(source_cct, source_cct, return_as="score"), 2)
|
||||
== 1.0
|
||||
)
|
||||
assert (
|
||||
round(
|
||||
text_extraction.calculate_edit_distance(
|
||||
source_cct_word_space,
|
||||
source_cct,
|
||||
return_as="score",
|
||||
),
|
||||
2,
|
||||
)
|
||||
== 0.75
|
||||
)
|
||||
assert (
|
||||
round(
|
||||
text_extraction.calculate_edit_distance(
|
||||
source_cct_spaces,
|
||||
source_cct,
|
||||
return_as="score",
|
||||
),
|
||||
2,
|
||||
)
|
||||
== 0.39
|
||||
)
|
||||
assert (
|
||||
round(
|
||||
text_extraction.calculate_edit_distance(
|
||||
source_cct_no_space,
|
||||
source_cct,
|
||||
return_as="score",
|
||||
),
|
||||
2,
|
||||
)
|
||||
== 0.64
|
||||
)
|
||||
assert (
|
||||
round(
|
||||
text_extraction.calculate_edit_distance(
|
||||
source_cct_one_sentence,
|
||||
source_cct,
|
||||
return_as="score",
|
||||
),
|
||||
2,
|
||||
)
|
||||
== 0.0
|
||||
)
|
||||
assert (
|
||||
round(
|
||||
text_extraction.calculate_edit_distance(
|
||||
source_cct_missing_word,
|
||||
source_cct,
|
||||
return_as="score",
|
||||
),
|
||||
2,
|
||||
)
|
||||
== 0.57
|
||||
)
|
||||
assert (
|
||||
round(
|
||||
text_extraction.calculate_edit_distance(
|
||||
source_cct_addn_char,
|
||||
source_cct,
|
||||
return_as="score",
|
||||
),
|
||||
2,
|
||||
)
|
||||
== 0.89
|
||||
)
|
||||
assert (
|
||||
round(
|
||||
text_extraction.calculate_edit_distance(
|
||||
source_cct_dup_word,
|
||||
source_cct,
|
||||
return_as="score",
|
||||
),
|
||||
2,
|
||||
)
|
||||
== 0.79
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("filename", "standardize_whitespaces", "expected_score", "expected_distance"),
|
||||
[
|
||||
("fake-text.txt", False, 0.78, 38),
|
||||
("fake-text.txt", True, 0.92, 12),
|
||||
],
|
||||
)
|
||||
def test_calculate_edit_distance_with_filename(
|
||||
filename, standardize_whitespaces, expected_score, expected_distance
|
||||
):
|
||||
with open("example-docs/fake-text.txt") as f:
|
||||
source_cct = f.read()
|
||||
|
||||
elements = partition(filename=f"example-docs/{filename}")
|
||||
output_cct = "\n".join([str(el) for el in elements])
|
||||
|
||||
score = text_extraction.calculate_edit_distance(
|
||||
output_cct, source_cct, return_as="score", standardize_whitespaces=standardize_whitespaces
|
||||
)
|
||||
distance = text_extraction.calculate_edit_distance(
|
||||
output_cct,
|
||||
source_cct,
|
||||
return_as="distance",
|
||||
standardize_whitespaces=standardize_whitespaces,
|
||||
)
|
||||
|
||||
assert score >= 0
|
||||
assert score <= 1.0
|
||||
assert distance >= 0
|
||||
assert round(score, 2) == expected_score
|
||||
assert distance == expected_distance
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("text1", "text2"),
|
||||
[
|
||||
(
|
||||
"The dog\rloved the cat, but\t\n the cat\tloved the\n cow",
|
||||
"The dog loved the cat, but the cat loved the cow",
|
||||
),
|
||||
(
|
||||
"Hello my\tname\tis H a r p e r, \nwhat's your\vname?",
|
||||
"Hello my name is H a r p e r, what's your name?",
|
||||
),
|
||||
(
|
||||
"I have a\t\n\tdog and a\tcat,\fI love my\n\n\n\ndog.",
|
||||
"I have a dog and a cat, I love my dog.",
|
||||
),
|
||||
(
|
||||
"""
|
||||
Name Age City Occupation
|
||||
Alice 30 New York Engineer
|
||||
Bob 25 Los Angeles Designer
|
||||
Charlie 35 Chicago Teacher
|
||||
David 40 San Francisco Developer
|
||||
""",
|
||||
"""
|
||||
Name\tAge\tCity\tOccupation
|
||||
Alice\t30\tNew York\tEngineer
|
||||
Bob\t25\tLos Angeles\tDesigner
|
||||
Charlie\t35\tChicago\tTeacher
|
||||
David\t40\tSan Francisco\tDeveloper
|
||||
""",
|
||||
),
|
||||
(
|
||||
"""
|
||||
Name\tAge\tCity\tOccupation
|
||||
Alice\t30\tNew York\tEngineer
|
||||
Bob\t25\tLos Angeles\tDesigner
|
||||
Charlie\t35\tChicago\tTeacher
|
||||
David\t40\tSan Francisco\tDeveloper
|
||||
""",
|
||||
"Name\tAge\tCity\tOccupation\n\n \nAlice\t30\tNew York\tEngineer\nBob\t25\tLos Angeles\tDesigner\nCharlie\t35\tChicago\tTeacher\nDavid\t40\tSan Francisco\tDeveloper", # noqa: E501
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_calculate_edit_distance_with_various_whitespace_1(text1, text2):
|
||||
assert (
|
||||
text_extraction.calculate_edit_distance(
|
||||
text1, text2, return_as="score", standardize_whitespaces=True
|
||||
)
|
||||
== 1.0
|
||||
)
|
||||
assert (
|
||||
text_extraction.calculate_edit_distance(
|
||||
text1, text2, return_as="distance", standardize_whitespaces=True
|
||||
)
|
||||
== 0
|
||||
)
|
||||
assert (
|
||||
text_extraction.calculate_edit_distance(
|
||||
text1, text2, return_as="score", standardize_whitespaces=False
|
||||
)
|
||||
< 1.0
|
||||
)
|
||||
assert (
|
||||
text_extraction.calculate_edit_distance(
|
||||
text1, text2, return_as="distance", standardize_whitespaces=False
|
||||
)
|
||||
> 0
|
||||
)
|
||||
|
||||
|
||||
def test_calculate_edit_distance_with_various_whitespace_2():
|
||||
source_cct_tabs = """
|
||||
Name\tAge\tCity\tOccupation
|
||||
Alice\t30\tNew York\tEngineer
|
||||
Bob\t25\tLos Angeles\tDesigner
|
||||
Charlie\t35\tChicago\tTeacher
|
||||
David\t40\tSan Francisco\tDeveloper
|
||||
"""
|
||||
source_cct_with_borders = """
|
||||
|
||||
| Name | Age | City | Occupation |
|
||||
|---------|-----|--------------|----------------|
|
||||
| Alice | 30 | New York | Engineer |
|
||||
| Bob | 25 | Los Angeles | Designer |
|
||||
| Charlie | 35 | Chicago | Teacher |
|
||||
| David | 40 | San Francisco| Developer |
|
||||
|
||||
"""
|
||||
assert text_extraction.calculate_edit_distance(
|
||||
source_cct_tabs, source_cct_with_borders, return_as="score", standardize_whitespaces=True
|
||||
) > text_extraction.calculate_edit_distance(
|
||||
source_cct_tabs, source_cct_with_borders, return_as="score", standardize_whitespaces=False
|
||||
)
|
||||
assert text_extraction.calculate_edit_distance(
|
||||
source_cct_tabs, source_cct_with_borders, return_as="distance", standardize_whitespaces=True
|
||||
) < text_extraction.calculate_edit_distance(
|
||||
source_cct_tabs,
|
||||
source_cct_with_borders,
|
||||
return_as="distance",
|
||||
standardize_whitespaces=False,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("text", "expected"),
|
||||
[
|
||||
(
|
||||
"The dog loved the cat, but the cat loved the cow",
|
||||
{"the": 4, "cat": 2, "loved": 2, "dog": 1, "but": 1, "cow": 1},
|
||||
),
|
||||
(
|
||||
"Hello my name is H a r p e r, what's your name?",
|
||||
{"hello": 1, "my": 1, "name": 2, "is": 1, "what's": 1, "your": 1},
|
||||
),
|
||||
(
|
||||
"I have a dog and a cat, I love my dog.",
|
||||
{"i": 2, "have": 1, "a": 2, "dog": 2, "and": 1, "cat": 1, "love": 1, "my": 1},
|
||||
),
|
||||
(
|
||||
"My dog's hair is red, but the dogs' houses are blue.",
|
||||
{
|
||||
"my": 1,
|
||||
"dog's": 1,
|
||||
"hair": 1,
|
||||
"is": 1,
|
||||
"red": 1,
|
||||
"but": 1,
|
||||
"the": 1,
|
||||
"dogs'": 1,
|
||||
"houses": 1,
|
||||
"are": 1,
|
||||
"blue": 1,
|
||||
},
|
||||
),
|
||||
(
|
||||
"""Sometimes sentences have a dash - like this one!
|
||||
A hyphen connects 2 words with no gap: easy-peasy.""",
|
||||
{
|
||||
"sometimes": 1,
|
||||
"sentences": 1,
|
||||
"have": 1,
|
||||
"a": 2,
|
||||
"dash": 1,
|
||||
"like": 1,
|
||||
"this": 1,
|
||||
"one": 1,
|
||||
"hyphen": 1,
|
||||
"connects": 1,
|
||||
"2": 1,
|
||||
"words": 1,
|
||||
"with": 1,
|
||||
"no": 1,
|
||||
"gap": 1,
|
||||
"easy-peasy": 1,
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_bag_of_words(text, expected):
|
||||
assert text_extraction.bag_of_words(text) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("text", "expected"),
|
||||
[
|
||||
(
|
||||
"The dog\rloved the cat, but\t\n the cat\tloved the\n cow\n\n",
|
||||
"The dog loved the cat, but the cat loved the cow",
|
||||
),
|
||||
(
|
||||
"\n\nHello my\tname\tis H a r p e r, \nwhat's your\vname?",
|
||||
"Hello my name is H a r p e r, what's your name?",
|
||||
),
|
||||
(
|
||||
"I have a\t\n\tdog and a\tcat,\fI love my\n\n\n\ndog.",
|
||||
"I have a dog and a cat, I love my dog.",
|
||||
),
|
||||
(
|
||||
"""L is for the way you look at me
|
||||
O is for the only one I see
|
||||
V is very, very extraordinary
|
||||
E is even more than anyone that you adore can""",
|
||||
"L is for the way you look at me O is for the only one I see V is very, very extraordinary E is even more than anyone that you adore can", # noqa: E501
|
||||
),
|
||||
(
|
||||
"""
|
||||
| Name | Age | City | Occupation |
|
||||
|---------|-----|--------------|----------------|
|
||||
| Alice | 30 | New York | Engineer |
|
||||
| Bob | 25 | Los Angeles | Designer |
|
||||
| Charlie | 35 | Chicago | Teacher |
|
||||
| David | 40 | San Francisco| Developer |
|
||||
""",
|
||||
"| Name | Age | City | Occupation | |---------|-----|--------------|----------------| | Alice | 30 | New York | Engineer | | Bob | 25 | Los Angeles | Designer | | Charlie | 35 | Chicago | Teacher | | David | 40 | San Francisco| Developer |", # noqa: E501
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_prepare_string(text, expected):
|
||||
assert text_extraction.prepare_str(text, standardize_whitespaces=True) == expected
|
||||
assert text_extraction.prepare_str(text) == text
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("input_text", "expected_output"),
|
||||
[
|
||||
# Mixed quotes in longer sentences
|
||||
(
|
||||
"She said \"Hello\" and then whispered 'Goodbye' before leaving.",
|
||||
"She said \"Hello\" and then whispered 'Goodbye' before leaving.",
|
||||
),
|
||||
# Double low-9 quotes with complex content
|
||||
(
|
||||
"„To be, or not to be, that is the question\" - Shakespeare's famous quote.",
|
||||
'"To be, or not to be, that is the question" - Shakespeare\'s famous quote.',
|
||||
),
|
||||
# Angle quotes with nested quotes
|
||||
(
|
||||
'«When he said "life is beautiful," I believed him» wrote Maria.',
|
||||
'"When he said "life is beautiful," I believed him" wrote Maria.',
|
||||
),
|
||||
# Heavy ornament quotes in dialogue
|
||||
(
|
||||
"❝Do you remember when we first met?❞ she asked with a smile.",
|
||||
'"Do you remember when we first met?" she asked with a smile.',
|
||||
),
|
||||
# Double prime quotes with punctuation
|
||||
(
|
||||
"〝The meeting starts at 10:00, don't be late!〟 announced the manager.",
|
||||
'"The meeting starts at 10:00, don\'t be late!" announced the manager.',
|
||||
),
|
||||
# Corner brackets with nested quotes
|
||||
(
|
||||
'「He told me "This is important" yesterday」, she explained.',
|
||||
"'He told me \"This is important\" yesterday', she explained.",
|
||||
),
|
||||
# White corner brackets with multiple sentences
|
||||
(
|
||||
"『The sun was setting. The birds were singing. It was peaceful.』",
|
||||
"'The sun was setting. The birds were singing. It was peaceful.'",
|
||||
),
|
||||
# Vertical corner brackets with numbers and special characters
|
||||
("﹂Meeting #123 @ 15:00 - Don't forget!﹁", "'Meeting #123 @ 15:00 - Don't forget!'"),
|
||||
# Complex mixed quote types
|
||||
(
|
||||
'「Hello」, ❝World❞, "Test", \'Example\', „Quote", «Final»',
|
||||
'\'Hello\', "World", "Test", \'Example\', "Quote", "Final"',
|
||||
),
|
||||
# Quotes with multiple apostrophes
|
||||
("It's John's book, isn't it?", "It's John's book, isn't it?"),
|
||||
# Single angle quotes with nested content
|
||||
(
|
||||
'‹Testing the system\'s capability for "quoted" text›',
|
||||
"'Testing the system's capability for \"quoted\" text'",
|
||||
),
|
||||
# Heavy single ornament quotes with multiple sentences
|
||||
(
|
||||
"❛First sentence. Second sentence. Third sentence.❜",
|
||||
"'First sentence. Second sentence. Third sentence.'",
|
||||
),
|
||||
# Mix of various quote types in complex text
|
||||
(
|
||||
'「Chapter 1」: ❝The Beginning❞ - „A new story" begins «today».',
|
||||
'\'Chapter 1\': "The Beginning" - "A new story" begins "today".',
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_standardize_quotes(input_text, expected_output):
|
||||
assert text_extraction.standardize_quotes(input_text) == expected_output
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("output_text", "source_text", "expected_percentage"),
|
||||
[
|
||||
(
|
||||
"extra",
|
||||
"",
|
||||
0,
|
||||
),
|
||||
(
|
||||
"",
|
||||
"Source text has a sentence.",
|
||||
1,
|
||||
),
|
||||
(
|
||||
"The original s e n t e n c e is normal.",
|
||||
"The original sentence is normal...",
|
||||
0.2,
|
||||
),
|
||||
(
|
||||
"We saw 23% improvement in this quarter.",
|
||||
"We saw 23% improvement in sales this quarter.",
|
||||
0.125,
|
||||
),
|
||||
(
|
||||
"no",
|
||||
"Is it possible to have more than everything missing?",
|
||||
1,
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_calculate_percent_missing_text(output_text, source_text, expected_percentage):
|
||||
assert (
|
||||
text_extraction.calculate_percent_missing_text(output_text, source_text)
|
||||
== expected_percentage
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("table_as_cells", "expected_extraction"),
|
||||
[
|
||||
pytest.param(
|
||||
[
|
||||
{"x": 0, "y": 0, "w": 1, "h": 1, "content": "Month A."},
|
||||
{"x": 0, "y": 1, "w": 1, "h": 1, "content": "22"},
|
||||
],
|
||||
[
|
||||
{"row_index": 0, "col_index": 0, "content": "Month A."},
|
||||
{"row_index": 1, "col_index": 0, "content": "22"},
|
||||
],
|
||||
id="Simple table, 1 head cell, 1 body cell, no spans",
|
||||
),
|
||||
pytest.param(
|
||||
[
|
||||
{"x": 0, "y": 0, "w": 1, "h": 1, "content": "Month A."},
|
||||
{"x": 1, "y": 0, "w": 1, "h": 1, "content": "Month B."},
|
||||
{"x": 2, "y": 0, "w": 1, "h": 1, "content": "Month C."},
|
||||
{"x": 0, "y": 1, "w": 1, "h": 1, "content": "11"},
|
||||
{"x": 1, "y": 1, "w": 1, "h": 1, "content": "12"},
|
||||
{"x": 2, "y": 1, "w": 1, "h": 1, "content": "13"},
|
||||
{"x": 0, "y": 2, "w": 1, "h": 1, "content": "21"},
|
||||
{"x": 1, "y": 2, "w": 1, "h": 1, "content": "22"},
|
||||
{"x": 2, "y": 2, "w": 1, "h": 1, "content": "23"},
|
||||
],
|
||||
[
|
||||
{"row_index": 0, "col_index": 0, "content": "Month A."},
|
||||
{"row_index": 0, "col_index": 1, "content": "Month B."},
|
||||
{"row_index": 0, "col_index": 2, "content": "Month C."},
|
||||
{"row_index": 1, "col_index": 0, "content": "11"},
|
||||
{"row_index": 1, "col_index": 1, "content": "12"},
|
||||
{"row_index": 1, "col_index": 2, "content": "13"},
|
||||
{"row_index": 2, "col_index": 0, "content": "21"},
|
||||
{"row_index": 2, "col_index": 1, "content": "22"},
|
||||
{"row_index": 2, "col_index": 2, "content": "23"},
|
||||
],
|
||||
id="Simple table, 3 head cell, 5 body cell, no spans",
|
||||
),
|
||||
# +----------+---------------------+----------+
|
||||
# | | h1col23 | h1col4 |
|
||||
# | h12col1 |----------+----------+----------|
|
||||
# | | h2col2 | h2col34 |
|
||||
# |----------|----------+----------+----------+
|
||||
# | r3col1 | r3col2 | |
|
||||
# |----------+----------| r34col34 |
|
||||
# | r4col12 | |
|
||||
# +----------+----------+----------+----------+
|
||||
pytest.param(
|
||||
[
|
||||
{
|
||||
"y": 0,
|
||||
"x": 0,
|
||||
"w": 2,
|
||||
"h": 1,
|
||||
"content": "h12col1",
|
||||
},
|
||||
{
|
||||
"y": 0,
|
||||
"x": 1,
|
||||
"w": 1,
|
||||
"h": 2,
|
||||
"content": "h1col23",
|
||||
},
|
||||
{
|
||||
"y": 0,
|
||||
"x": 3,
|
||||
"w": 1,
|
||||
"h": 1,
|
||||
"content": "h1col4",
|
||||
},
|
||||
{
|
||||
"y": 1,
|
||||
"x": 1,
|
||||
"w": 1,
|
||||
"h": 1,
|
||||
"content": "h2col2",
|
||||
},
|
||||
{
|
||||
"y": 1,
|
||||
"x": 2,
|
||||
"w": 1,
|
||||
"h": 2,
|
||||
"content": "h2col34",
|
||||
},
|
||||
{
|
||||
"y": 2,
|
||||
"x": 0,
|
||||
"w": 1,
|
||||
"h": 1,
|
||||
"content": "r3col1",
|
||||
},
|
||||
{
|
||||
"y": 2,
|
||||
"x": 1,
|
||||
"w": 1,
|
||||
"h": 1,
|
||||
"content": "r3col2",
|
||||
},
|
||||
{
|
||||
"y": 2,
|
||||
"x": 2,
|
||||
"w": 2,
|
||||
"h": 2,
|
||||
"content": "r34col34",
|
||||
},
|
||||
{
|
||||
"y": 3,
|
||||
"x": 0,
|
||||
"w": 1,
|
||||
"h": 2,
|
||||
"content": "r4col12",
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
"row_index": 0,
|
||||
"col_index": 0,
|
||||
"content": "h12col1",
|
||||
},
|
||||
{
|
||||
"row_index": 0,
|
||||
"col_index": 1,
|
||||
"content": "h1col23",
|
||||
},
|
||||
{
|
||||
"row_index": 0,
|
||||
"col_index": 3,
|
||||
"content": "h1col4",
|
||||
},
|
||||
{
|
||||
"row_index": 1,
|
||||
"col_index": 1,
|
||||
"content": "h2col2",
|
||||
},
|
||||
{
|
||||
"row_index": 1,
|
||||
"col_index": 2,
|
||||
"content": "h2col34",
|
||||
},
|
||||
{
|
||||
"row_index": 2,
|
||||
"col_index": 0,
|
||||
"content": "r3col1",
|
||||
},
|
||||
{
|
||||
"row_index": 2,
|
||||
"col_index": 1,
|
||||
"content": "r3col2",
|
||||
},
|
||||
{
|
||||
"row_index": 2,
|
||||
"col_index": 2,
|
||||
"content": "r34col34",
|
||||
},
|
||||
{
|
||||
"row_index": 3,
|
||||
"col_index": 0,
|
||||
"content": "r4col12",
|
||||
},
|
||||
],
|
||||
id="various spans, with 2 row header",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_cells_table_extraction_from_prediction(table_as_cells, expected_extraction):
|
||||
example_element = {
|
||||
"type": "Table",
|
||||
"metadata": {"table_as_cells": table_as_cells},
|
||||
}
|
||||
assert extract_cells_from_table_as_cells(example_element) == expected_extraction
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("text_as_html", "expected_extraction"),
|
||||
[
|
||||
pytest.param(
|
||||
"""
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Month A.</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>22</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>"
|
||||
""",
|
||||
[
|
||||
{"row_index": 0, "col_index": 0, "content": "Month A."},
|
||||
{"row_index": 1, "col_index": 0, "content": "22"},
|
||||
],
|
||||
id="Simple table, 1 head cell, 1 body cell, no spans",
|
||||
),
|
||||
pytest.param(
|
||||
"""
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Month A.</th>
|
||||
<th>Month B.</th>
|
||||
<th>Month C.</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>11</td>
|
||||
<td>12</td>
|
||||
<td>13</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>21</td>
|
||||
<td>22</td>
|
||||
<td>23</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>"
|
||||
""",
|
||||
[
|
||||
{"row_index": 0, "col_index": 0, "content": "Month A."},
|
||||
{"row_index": 0, "col_index": 1, "content": "Month B."},
|
||||
{"row_index": 0, "col_index": 2, "content": "Month C."},
|
||||
{"row_index": 1, "col_index": 0, "content": "11"},
|
||||
{"row_index": 1, "col_index": 1, "content": "12"},
|
||||
{"row_index": 1, "col_index": 2, "content": "13"},
|
||||
{"row_index": 2, "col_index": 0, "content": "21"},
|
||||
{"row_index": 2, "col_index": 1, "content": "22"},
|
||||
{"row_index": 2, "col_index": 2, "content": "23"},
|
||||
],
|
||||
id="Simple table, 3 head cell, 5 body cell, no spans",
|
||||
),
|
||||
# +----------+---------------------+----------+
|
||||
# | | h1col23 | h1col4 |
|
||||
# | h12col1 |----------+----------+----------|
|
||||
# | | h2col2 | h2col34 |
|
||||
# |----------|----------+----------+----------+
|
||||
# | r3col1 | r3col2 | |
|
||||
# |----------+----------| r34col34 |
|
||||
# | r4col12 | |
|
||||
# +----------+----------+----------+----------+
|
||||
pytest.param(
|
||||
"""
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th rowspan="2">h12col1</th>
|
||||
<th colspan="2">h1col23</th>
|
||||
<th>h1col4</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>h2col2</th>
|
||||
<th colspan="2">h2col34</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>r3col1</td>
|
||||
<td>r3col2</td>
|
||||
<td colspan="2" rowspan="2">r34col34</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">r4col12</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
""",
|
||||
[
|
||||
{
|
||||
"row_index": 0,
|
||||
"col_index": 0,
|
||||
"content": "h12col1",
|
||||
},
|
||||
{
|
||||
"row_index": 0,
|
||||
"col_index": 1,
|
||||
"content": "h1col23",
|
||||
},
|
||||
{
|
||||
"row_index": 0,
|
||||
"col_index": 3,
|
||||
"content": "h1col4",
|
||||
},
|
||||
{
|
||||
"row_index": 1,
|
||||
"col_index": 1,
|
||||
"content": "h2col2",
|
||||
},
|
||||
{
|
||||
"row_index": 1,
|
||||
"col_index": 2,
|
||||
"content": "h2col34",
|
||||
},
|
||||
{
|
||||
"row_index": 2,
|
||||
"col_index": 0,
|
||||
"content": "r3col1",
|
||||
},
|
||||
{
|
||||
"row_index": 2,
|
||||
"col_index": 1,
|
||||
"content": "r3col2",
|
||||
},
|
||||
{
|
||||
"row_index": 2,
|
||||
"col_index": 2,
|
||||
"content": "r34col34",
|
||||
},
|
||||
{
|
||||
"row_index": 3,
|
||||
"col_index": 0,
|
||||
"content": "r4col12",
|
||||
},
|
||||
],
|
||||
id="various spans, with 2 row header",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_html_table_extraction_from_prediction(text_as_html, expected_extraction):
|
||||
example_element = {
|
||||
"type": "Table",
|
||||
"metadata": {
|
||||
"text_as_html": text_as_html,
|
||||
},
|
||||
}
|
||||
assert extract_cells_from_text_as_html(example_element) == expected_extraction
|
||||
|
||||
|
||||
def test_cells_extraction_from_prediction_when_missing_prediction():
|
||||
example_element = {"type": "Table", "metadata": {"text_as_html": "", "table_as_cells": []}}
|
||||
assert extract_cells_from_text_as_html(example_element) is None
|
||||
assert extract_cells_from_table_as_cells(example_element) is None
|
||||
|
||||
|
||||
def _trim_html(html: str) -> str:
|
||||
html_lines = [line.strip() for line in html.split("\n") if line]
|
||||
return "".join(html_lines)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"html_to_test",
|
||||
[
|
||||
"""
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Month A.</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>22</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
""",
|
||||
"""
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Month A.</th>
|
||||
<th>Month B.</th>
|
||||
<th>Month C.</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>11</td>
|
||||
<td>12</td>
|
||||
<td>13</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>21</td>
|
||||
<td>22</td>
|
||||
<td>23</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
""",
|
||||
"""
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th rowspan="2">h12col1</th>
|
||||
<th colspan="2">h1col23</th>
|
||||
<th>h1col4</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>h2col2</th>
|
||||
<th colspan="2">h2col34</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>r3col1</td>
|
||||
<td>r3col2</td>
|
||||
<td colspan="2" rowspan="2">r34col34</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">r4col12</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
""",
|
||||
],
|
||||
)
|
||||
def test_deckerd_html_converter(html_to_test):
|
||||
deckerd_table = html_table_to_deckerd(html_to_test)
|
||||
html_table = deckerd_table_to_html(deckerd_table)
|
||||
assert _trim_html(html_to_test) == html_table
|
||||
@@ -0,0 +1,35 @@
|
||||
import pytest
|
||||
|
||||
from unstructured.metrics.utils import (
|
||||
_mean,
|
||||
_pstdev,
|
||||
_stdev,
|
||||
_uniquity_file,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("numbers", "expected_mean", "expected_stdev", "expected_pstdev"),
|
||||
[
|
||||
([2, 5, 6, 7], 5, 2.16, 1.871),
|
||||
([1, 100], 50.5, 70.004, 49.5),
|
||||
([1], 1, None, None),
|
||||
([], None, None, None),
|
||||
],
|
||||
)
|
||||
def test_stats(numbers, expected_mean, expected_stdev, expected_pstdev):
|
||||
mean = _mean(numbers)
|
||||
stdev = _stdev(numbers)
|
||||
pstdev = _pstdev(numbers)
|
||||
assert mean == expected_mean
|
||||
assert stdev == expected_stdev
|
||||
assert pstdev == expected_pstdev
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("filenames"),
|
||||
[("filename.ext", "filename (1).ext", "randomfile.ext", "filename.txt", "filename (5).txt")],
|
||||
)
|
||||
def test_uniquity_file(filenames):
|
||||
final_filename = _uniquity_file(filenames, "filename.ext")
|
||||
assert final_filename == "filename (2).ext"
|
||||
Reference in New Issue
Block a user