refactoring: splitting conftest logic into submodules

This commit is contained in:
Matthias Bisping 2022-04-14 18:03:19 +02:00
parent 13513db5a1
commit db2b85382b
10 changed files with 118 additions and 95 deletions

View File

@ -3,24 +3,19 @@ import logging
import os import os
import random import random
import string import string
import tempfile
from functools import partial from functools import partial
from itertools import starmap from itertools import starmap
from operator import itemgetter from operator import itemgetter
from typing import Iterable
import fpdf import fpdf
import numpy as np import numpy as np
import pytest import pytest
from PIL import Image
from frozendict import frozendict
from funcy import rcompose, merge from funcy import rcompose, merge
from image_prediction.classifier.classifier import Classifier from image_prediction.classifier.classifier import Classifier
from image_prediction.classifier.image_classifier import ImageClassifier from image_prediction.classifier.image_classifier import ImageClassifier
from image_prediction.estimator.adapter.adapter import EstimatorAdapter from image_prediction.estimator.adapter.adapter import EstimatorAdapter
from image_prediction.estimator.preprocessor.preprocessors.basic import BasicPreprocessor from image_prediction.estimator.preprocessor.preprocessors.basic import BasicPreprocessor
from image_prediction.estimator.preprocessor.utils import image_to_normalized_tensor
from image_prediction.exceptions import ( from image_prediction.exceptions import (
UnknownEstimatorAdapter, UnknownEstimatorAdapter,
UnknownImageExtractor, UnknownImageExtractor,
@ -41,6 +36,11 @@ from image_prediction.pipeline import load_pipeline
from image_prediction.redai_adapter.mlflow import MlflowModelReader from image_prediction.redai_adapter.mlflow import MlflowModelReader
from image_prediction.redai_adapter.model import PredictionModelHandle from image_prediction.redai_adapter.model import PredictionModelHandle
from image_prediction.utils import get_logger from image_prediction.utils import get_logger
from test.utils.generation.image import array_to_image
from test.utils.generation.pdf import add_image, pdf_stream
pytest_plugins = ['test.utils.model']
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
@ -75,23 +75,6 @@ def classifier(estimator_adapter, label_mapper):
return classifier return classifier
@pytest.fixture
def estimator_mock():
class EstimatorMock:
@staticmethod
def predict(batch):
return [None for _ in batch]
@staticmethod
def predict_proba(batch):
return [None for _ in batch]
def __call__(self, batch):
return self.predict(batch)
return EstimatorMock()
@pytest.fixture @pytest.fixture
def label_mapper(label_format, classes): def label_mapper(label_format, classes):
if label_format == "index": if label_format == "index":
@ -205,21 +188,6 @@ def __input_size(request):
return itemgetter("width", "height", "depth")(request.param) return itemgetter("width", "height", "depth")(request.param)
def array_to_image(array):
assert np.all(array <= 1)
assert np.all(array >= 0)
if array.shape[-1] == 3:
mode = "RGB"
elif array.shape[-1] == 4:
mode = "RGBA"
else:
raise ValueError(f"Unexpected number of channels {array.shape[-1]}. Expected 3 or 4.")
# noinspection PyTypeChecker
return Image.fromarray(np.uint8(array * 255), mode=mode)
@pytest.fixture @pytest.fixture
def batch_of_expected_string_labels(batch_of_expected_numeric_labels, classes): def batch_of_expected_string_labels(batch_of_expected_numeric_labels, classes):
return map_labels(batch_of_expected_numeric_labels, classes) return map_labels(batch_of_expected_numeric_labels, classes)
@ -340,30 +308,6 @@ def pdf(image_metadata_pairs):
return pdf_stream(pdf) return pdf_stream(pdf)
def add_image(pdf, image_metadata_pair, suffix="png"):
while fewer_pages_then_required(image_metadata_pair.metadata[Info.PAGE_IDX], pdf):
pdf.add_page()
add_image_to_last_page(pdf, image_metadata_pair, suffix=suffix)
def fewer_pages_then_required(page_idx, pdf):
return page_idx > pdf.page - 1
def pdf_stream(pdf: fpdf.fpdf.FPDF):
return pdf.output(dest="S").encode("latin1")
def add_image_to_last_page(pdf: fpdf.fpdf.FPDF, image_metadata_pair, suffix):
image, metadata = image_metadata_pair
x, y, w, h = itemgetter(Info.X1, Info.Y1, Info.WIDTH, Info.HEIGHT)(metadata)
with tempfile.NamedTemporaryFile(suffix=f".{suffix}") as temp_image:
image.save(temp_image.name)
pdf.image(temp_image.name, x=x, y=y, w=w, h=h, type=suffix)
@pytest.fixture @pytest.fixture
def model(): def model():
class Model: class Model:
@ -465,10 +409,6 @@ def pipeline():
return pipeline return pipeline
def transform_equal(a, b):
return (list(a) if isinstance(a, map) else a) == b
def get_base_position_metadata(width, height, page_width, page_height): def get_base_position_metadata(width, height, page_width, page_height):
return { return {
Info.WIDTH: width, Info.WIDTH: width,
@ -504,23 +444,3 @@ def page_height(request):
@pytest.fixture(params=[100, 310]) @pytest.fixture(params=[100, 310])
def page_width(request): def page_width(request):
return request.param return request.param
def random_single_color_image_from_metadata(metadata):
image = Image.new(
"RGB", (metadata[Info.WIDTH], metadata[Info.HEIGHT]), color=tuple(map(int, np.random.uniform(size=3) * 255))
)
return image
def gray_image_from_metadata(metadata):
image = Image.new("RGB", (metadata[Info.WIDTH], metadata[Info.HEIGHT]), color=(100, 100, 100))
return image
def images_equal(im1: Image, im2: Image, **kwargs):
return np.allclose(image_to_normalized_tensor(im1), image_to_normalized_tensor(im2), **kwargs)
def metadata_equal(mdat1: Iterable, mdat2: Iterable):
return set(map(frozendict, mdat1)) == set(map(frozendict, mdat2))

View File

@ -4,7 +4,7 @@ from image_prediction.compositor.compositor import TransformerCompositor
from image_prediction.formatter.formatters.camel_case import Snake2CamelCaseKeyFormatter from image_prediction.formatter.formatters.camel_case import Snake2CamelCaseKeyFormatter
from image_prediction.formatter.formatters.enum import EnumFormatter from image_prediction.formatter.formatters.enum import EnumFormatter
from image_prediction.formatter.formatters.identity import IdentityFormatter from image_prediction.formatter.formatters.identity import IdentityFormatter
from test.conftest import transform_equal from test.utils.comparison import transform_equal
def test_identity(metadata): def test_identity(metadata):

View File

@ -12,7 +12,10 @@ from image_prediction.info import Info
from image_prediction.transformer.transformers.coordinate.fitz import FitzCoordinateTransformer from image_prediction.transformer.transformers.coordinate.fitz import FitzCoordinateTransformer
from image_prediction.transformer.transformers.coordinate.fpdf import FPDFCoordinateTransformer from image_prediction.transformer.transformers.coordinate.fpdf import FPDFCoordinateTransformer
from image_prediction.transformer.transformers.coordinate.pdfnet import PDFNetCoordinateTransformer from image_prediction.transformer.transformers.coordinate.pdfnet import PDFNetCoordinateTransformer
from test.conftest import array_to_image, add_image, transform_equal, get_base_position_metadata from test.conftest import get_base_position_metadata
from test.utils.generation.image import array_to_image
from test.utils.generation.pdf import add_image
from test.utils.comparison import transform_equal
@pytest.mark.parametrize("coordinate_system", ["fpdf"]) @pytest.mark.parametrize("coordinate_system", ["fpdf"])

View File

@ -11,7 +11,8 @@ from image_prediction.extraction import extract_images_from_pdf
from image_prediction.image_extractor.extractor import ImageMetadataPair from image_prediction.image_extractor.extractor import ImageMetadataPair
from image_prediction.image_extractor.extractors.parsable import extract_pages, get_image_infos, has_alpha_channel from image_prediction.image_extractor.extractors.parsable import extract_pages, get_image_infos, has_alpha_channel
from image_prediction.info import Info from image_prediction.info import Info
from test.conftest import add_image, pdf_stream, images_equal, metadata_equal from test.utils.generation.pdf import add_image, pdf_stream
from test.utils.comparison import images_equal, metadata_equal, image_sets_equal
@pytest.mark.parametrize("extractor_type", ["mock"]) @pytest.mark.parametrize("extractor_type", ["mock"])
@ -27,7 +28,7 @@ def test_image_extractor_mock(image_extractor, images):
def test_parsable_pdf_image_extractor(image_extractor, pdf, images, metadata, input_size, alpha): def test_parsable_pdf_image_extractor(image_extractor, pdf, images, metadata, input_size, alpha):
images_extracted, metadata_extracted = map(list, extract_images_from_pdf(pdf, image_extractor)) images_extracted, metadata_extracted = map(list, extract_images_from_pdf(pdf, image_extractor))
if not alpha: if not alpha:
all(any(images_equal(imex, im) for im in images) for imex in images_extracted) assert image_sets_equal(images_extracted, images)
assert metadata_equal(metadata_extracted, metadata) assert metadata_equal(metadata_extracted, metadata)

View File

@ -30,12 +30,9 @@ from image_prediction.stitching.utils import (
make_coord_getter, make_coord_getter,
make_length_getter, make_length_getter,
) )
from test.conftest import ( from test.utils.generation.pdf import add_image
add_image, from test.utils.generation.image import random_single_color_image_from_metadata, gray_image_from_metadata
random_single_color_image_from_metadata, from test.utils.comparison import images_equal
gray_image_from_metadata,
images_equal,
)
from test.utils.stitching import BoxSplitter from test.utils.stitching import BoxSplitter
x1_getter, y1_getter, x2_getter, y2_getter = map(make_coord_getter, ("x1", "y1", "x2", "y2")) x1_getter, y1_getter, x2_getter, y2_getter = map(make_coord_getter, ("x1", "y1", "x2", "y2"))

23
test/utils/comparison.py Normal file
View File

@ -0,0 +1,23 @@
from typing import Iterable
import numpy as np
from PIL import Image
from frozendict import frozendict
from image_prediction.estimator.preprocessor.utils import image_to_normalized_tensor
def transform_equal(a, b):
return (list(a) if isinstance(a, map) else a) == b
def images_equal(im1: Image, im2: Image, **kwargs):
return np.allclose(image_to_normalized_tensor(im1), image_to_normalized_tensor(im2), **kwargs)
def metadata_equal(mdat1: Iterable, mdat2: Iterable):
return set(map(frozendict, mdat1)) == set(map(frozendict, mdat2))
def image_sets_equal(ims1, ims2):
return all(any(images_equal(im1, im2) for im2 in ims2) for im1 in ims1)

View File

View File

@ -0,0 +1,31 @@
import numpy as np
from PIL import Image
from image_prediction.info import Info
def random_single_color_image_from_metadata(metadata):
image = Image.new(
"RGB", (metadata[Info.WIDTH], metadata[Info.HEIGHT]), color=tuple(map(int, np.random.uniform(size=3) * 255))
)
return image
def gray_image_from_metadata(metadata):
image = Image.new("RGB", (metadata[Info.WIDTH], metadata[Info.HEIGHT]), color=(100, 100, 100))
return image
def array_to_image(array):
assert np.all(array <= 1)
assert np.all(array >= 0)
if array.shape[-1] == 3:
mode = "RGB"
elif array.shape[-1] == 4:
mode = "RGBA"
else:
raise ValueError(f"Unexpected number of channels {array.shape[-1]}. Expected 3 or 4.")
# noinspection PyTypeChecker
return Image.fromarray(np.uint8(array * 255), mode=mode)

View File

@ -0,0 +1,30 @@
import tempfile
from operator import itemgetter
import fpdf
from image_prediction.info import Info
def add_image(pdf, image_metadata_pair, suffix="png"):
while fewer_pages_then_required(image_metadata_pair.metadata[Info.PAGE_IDX], pdf):
pdf.add_page()
add_image_to_last_page(pdf, image_metadata_pair, suffix=suffix)
def fewer_pages_then_required(page_idx, pdf):
return page_idx > pdf.page - 1
def pdf_stream(pdf: fpdf.fpdf.FPDF):
return pdf.output(dest="S").encode("latin1")
def add_image_to_last_page(pdf: fpdf.fpdf.FPDF, image_metadata_pair, suffix):
image, metadata = image_metadata_pair
x, y, w, h = itemgetter(Info.X1, Info.Y1, Info.WIDTH, Info.HEIGHT)(metadata)
with tempfile.NamedTemporaryFile(suffix=f".{suffix}") as temp_image:
image.save(temp_image.name)
pdf.image(temp_image.name, x=x, y=y, w=w, h=h, type=suffix)

18
test/utils/model.py Normal file
View File

@ -0,0 +1,18 @@
import pytest
@pytest.fixture
def estimator_mock():
class EstimatorMock:
@staticmethod
def predict(batch):
return [None for _ in batch]
@staticmethod
def predict_proba(batch):
return [None for _ in batch]
def __call__(self, batch):
return self.predict(batch)
return EstimatorMock()