Skip to main content

Module

from penquify.models.variation import PhotoVariation, Stain, PRESETS

Stain

@dataclass
class Stain:
    type: str = "coffee"              # coffee, water, grease, ink
    location: str = "upper_right"     # upper_right, center, lower_left, random
    opacity: str = "semi-transparent" # light, semi-transparent, heavy
    text_obstruction: str = "partial" # none, partial, severe

PhotoVariation

@dataclass
class PhotoVariation:
    name: str = "default"

    # Meta / camera
    camera: str = "Samsung Galaxy S8"
    year_device_style: str = "2017 Android"
    aspect_ratio: str = "4:3"
    capture_intent: str = "functional document photo"

    # Scene / framing
    document_coverage: str = "90% of frame"
    background: str = "blurred warehouse hints only at edges"

    # Paper deformation
    curvature: str = "slight"       # none, slight, strong
    folds: str = "none"             # none, middle_vertical, dog_ear, multiple
    wrinkles: str = "minor"         # none, minor, medium, heavy
    corner_bends: str = "none"
    edge_curl: str = "none"

    # Capture style
    angle: str = "slight oblique"   # straight, slight_oblique, strong_oblique_45deg
    skew: str = "slight"            # none, slight, moderate, strong
    rotation_degrees: float = 0     # 0-15
    focus_plane: str = "center sharp, edges softer"

    # Photo artifacts
    motion_blur: bool = False
    blur_direction: str = ""
    glare: str = "mild"             # none, mild, strong
    glare_location: str = ""
    shadow_from_hand: bool = True
    uneven_lighting: bool = True
    jpeg_compression: str = "light" # none, light, moderate, heavy

    # Hand presence
    hand_visible: bool = True
    grip_type: str = "thumb on lower corner"
    glove: str = "none"

    # Damage / contamination
    stain: Optional[Stain] = None
    dirt_marks: bool = False
    torn_edge: bool = False

    # Failure modes
    cropped_header: bool = False
    missing_area: str = ""
    overexposed_patch: bool = False
    shadow_band: bool = False

    # Multi-page
    stapled: bool = False
    stacked_sheets_behind: int = 0

Methods

to_prompt_json() -> dict

Convert the variation to the JSON format expected by the Gemini system instruction. Organizes fields into sections: meta, scene, subject, capture_style, photo_characteristics, and optionally damage, failure_modes, multi_page.

PRESETS

Dict of 8 built-in presets:
PRESETS = {
    "full_picture": PhotoVariation(
        name="full_picture",
        document_coverage="90% of frame",
        curvature="slight",
        angle="slight oblique",
        skew="slight",
    ),
    "folded_skewed": PhotoVariation(
        name="folded_skewed",
        document_coverage="88-93% of frame",
        curvature="strong",
        folds="dog_ear",
        angle="above-right perspective",
        skew="moderate",
        rotation_degrees=6,
    ),
    "zoomed_detail": PhotoVariation(
        name="zoomed_detail",
        document_coverage="95% of frame",
        angle="oblique 25-30 degrees",
        skew="slight",
        focus_plane="center text sharpest",
    ),
    "blurry": PhotoVariation(
        name="blurry",
        motion_blur=True,
        blur_direction="horizontal and downward",
        focus_plane="text-level softness overall",
    ),
    "cropped_header": PhotoVariation(
        name="cropped_header",
        cropped_header=True,
        missing_area="top 10-15% cut off",
    ),
    "strong_oblique": PhotoVariation(
        name="strong_oblique",
        curvature="strong",
        folds="middle_vertical",
        angle="45 degree oblique",
        skew="strong",
    ),
    "coffee_stain": PhotoVariation(
        name="coffee_stain",
        stain=Stain(
            type="coffee",
            location="upper_right",
            opacity="semi-transparent",
            text_obstruction="partial",
        ),
        wrinkles="medium",
    ),
    "stapled_stack": PhotoVariation(
        name="stapled_stack",
        stapled=True,
        stacked_sheets_behind=2,
        folds="dog_ear",
    ),
}