0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Pythonで画像モックアップの変更内容を検証する小さなメタデータ台帳

0
Posted at

A placement mockup can be easy to export and surprisingly difficult to review later. Two images arrive with similar filenames, but nobody remembers whether the artwork, crop, position, or display opacity changed. This is a small data-modeling problem before it is an image-processing problem. A structured comparison note can expose those differences without pretending to measure a body surface or decide whether a design is physically appropriate.

This article is project-affiliated with Tattoo Simulator and written with AI assistance. The Python example is an independent companion demonstration, not the source code of the website or a claim about its implementation. It reads no image pixels and performs no medical, professional tattooing, permission, or security assessment. All test identifiers and numeric settings below are synthetic examples, not records of real customers or evidence of product testing.

Model the statement that needs checking

The narrow question is whether the changes declared in a handoff note agree with the supplied metadata. If a note says that only position changed, but the artwork revision also changed, the discrepancy deserves attention. That does not prove that the image itself is misleading. It shows that the structured explanation and the supplied record disagree, which is a much smaller and more defensible conclusion for a simple validator.

The motivating tattoo overlay workflow combines a body photo with separate artwork and offers position, size, rotation, opacity, mirroring, reset, and PNG export. The homepage describes local placement and export separately from credit-based cover-concept generation. That makes comparison metadata a useful communication topic, but the example below does not connect to the website, inspect its internal state, or assume that it exports this proposed record format.

Official Tattoo Simulator public brand preview

Use explicit groups instead of a vague changed flag

A single boolean such as modified is not enough to explain a comparison. Position has two axes, while mirroring changes orientation in a way that should not be hidden inside rotation. A different source photo or artwork revision also matters even when every display setting is identical. Group names make these distinctions visible and allow a declaration to describe more than one intentional change without merging unrelated concepts.

The reference record uses short identifiers and finite display values. Those identifiers are labels, not file paths and not proof of anonymity. A person could still choose an identifying label, and a syntactically valid identifier does not establish permission to use an image. The code deliberately performs no file access. Do not turn this small identifier rule into a claim that it securely validates paths or that a metadata table is safe to publish.

ledger.py
"""Metadata-only comparison aid; no image, permission or physical validation."""
from dataclasses import dataclass
import math
import re

_ID = re.compile(r"[A-Za-z0-9_.-]{1,80}\Z")
GROUPS = {
    "photo": ("photo_id",),
    "artwork": ("artwork_id",),
    "position": ("x", "y"),
    "scale": ("scale",),
    "rotation": ("rotation",),
    "opacity": ("opacity",),
    "mirror": ("mirrored",),
}

@dataclass(frozen=True)
class Reference:
    photo_id: str
    artwork_id: str
    x: float = 0.0
    y: float = 0.0
    scale: float = 1.0
    rotation: float = 0.0
    opacity: float = 1.0
    mirrored: bool = False

    def __post_init__(self):
        for name in ("photo_id", "artwork_id"):
            value = getattr(self, name)
            if not isinstance(value, str) or not _ID.fullmatch(value):
                raise ValueError(name)
        for name in ("x", "y", "scale", "rotation", "opacity"):
            value = getattr(self, name)
            if type(value) not in (int, float) or not math.isfinite(value):
                raise ValueError(name)
        if self.scale <= 0 or not 0 <= self.opacity <= 1:
            raise ValueError("display range")
        if type(self.mirrored) is not bool:
            raise ValueError("mirrored")

def changes(before, after):
    return frozenset(
        group for group, fields in GROUPS.items()
        if any(getattr(before, f) != getattr(after, f) for f in fields)
    )

def declaration_issues(before, after, declared):
    declared = frozenset(declared)
    unknown = declared - GROUPS.keys()
    if unknown:
        raise ValueError("unknown change group")
    actual = changes(before, after)
    return {"unmentioned": sorted(actual - declared),
            "not_observed": sorted(declared - actual)}

Explain the invariants before trusting the output

The constructor rejects missing or malformed identifiers, nonfinite numeric settings, a nonpositive display scale, opacity outside the chosen interval, and a mirrored value that is not actually a boolean. These rules make the supplied record easier to reason about. They are not a statement about every editor's valid settings. A production adapter would need to translate its own interface values into an explicitly documented representation rather than assuming compatible units.

In Python, booleans are subclasses of integers, so a broad numeric check can accidentally accept True as a rotation. The example uses exact numeric types for that reason. It also checks finiteness because a not-a-number value has surprising equality behavior and can make a comparison report changes for the wrong reason. These decisions address consistency in the supplied metadata; they do not validate a photograph, a measurement, or a physical outcome.

The frozen dataclass discourages accidental edits to a record while it is being compared. It is a convenience for ordinary program structure, not a tamperproof audit log. It provides no identity verification, signature, cryptographic integrity, or protection against someone supplying false metadata. Keep that distinction explicit when a small programming example is reused in a larger workflow, especially if a reviewer starts treating a tidy report as evidence of authenticity.

Compare both directions of the declaration

There are two useful discrepancies. An unmentioned group changed according to the supplied record but was omitted from the declaration. A not-observed group was declared as changed even though the two supplied records agree on it. Reporting only the first case would miss an explanation that claims a rotation when the metadata shows only a position adjustment. Neither discrepancy should automatically be converted into a judgment about intent.

Unknown declaration groups are rejected rather than silently ignored. A label such as medical suitability would not become meaningful just because someone added it to a list. Keeping the vocabulary small helps preserve the boundary of the program. If another legitimate display property is later required, add it with a documented representation and a corresponding test, instead of accepting arbitrary labels that imply unsupported validation capabilities.

Make the tests small and descriptive

The tests below exercise the comparison behavior and several construction errors with synthetic records. They distinguish mirroring from rotation, group both position axes, keep a changed photo identifier visible, and check both declaration directions. The documented unittest command was run locally against these exact example files, with seven tests passing. That statement applies only to this companion code and does not claim testing of the website or of tattoo outcomes.

test_ledger.py
import unittest
from dataclasses import replace
from ledger import Reference, changes, declaration_issues

class LedgerTests(unittest.TestCase):
    def setUp(self):
        self.base = Reference("photo-base", "drawing-v1")

    def test_identical_metadata(self):
        self.assertEqual(changes(self.base, self.base), frozenset())

    def test_position_groups_both_axes(self):
        revised = replace(self.base, x=12, y=-3)
        self.assertEqual(changes(self.base, revised), {"position"})

    def test_mirroring_is_not_rotation(self):
        revised = replace(self.base, mirrored=True)
        self.assertEqual(changes(self.base, revised), {"mirror"})

    def test_photo_change_remains_visible(self):
        revised = replace(self.base, photo_id="photo-crop-b")
        self.assertEqual(changes(self.base, revised), {"photo"})

    def test_finite_and_display_ranges(self):
        for kwargs in ({"scale": 0}, {"x": float("nan")},
                       {"opacity": 1.1}, {"rotation": True}):
            with self.subTest(kwargs=kwargs), self.assertRaises(ValueError):
                replace(self.base, **kwargs)

    def test_identifier_and_boolean_validation(self):
        for kwargs in ({"photo_id": "../private"}, {"mirrored": 1}):
            with self.subTest(kwargs=kwargs), self.assertRaises(ValueError):
                replace(self.base, **kwargs)

    def test_declaration_reports_both_directions(self):
        revised = replace(self.base, rotation=15)
        self.assertEqual(
            declaration_issues(self.base, revised, {"position"}),
            {"unmentioned": ["rotation"], "not_observed": ["position"]})
        with self.assertRaises(ValueError):
            declaration_issues(self.base, revised, {"medical-suitability"})

if __name__ == "__main__":
    unittest.main()

Put both files in the same directory and run the command below from that directory. The example uses only the Python standard library. It needs no account, network request, image decoder, model provider, or personal photo. Running the tests is a way to inspect the stated invariants, not a substitute for reviewing the assumptions or extending the examples when a real application's data representation differs.

python3 -m unittest discover -s . -v

Keep comparison semantics separate from visual judgment

Equality here means equality of the supplied numeric values. It does not mean that two rendered images are visually identical. Conversely, a tiny numeric difference may not matter to the intended discussion. A real system might need a documented tolerance or normalization rule, but that rule should be chosen for the representation and task. Adding an arbitrary tolerance merely to suppress differences can hide a meaningful change rather than clarify it.

The example groups the two position axes because a handoff can naturally say that position changed. It keeps the source photo and artwork groups separate because changing those baselines can invalidate a simple comparison. The program cannot tell whether someone mislabeled a source file or reused an identifier for different content. Establishing stronger correspondence would require another design, and any resulting file fingerprints would still need privacy and permission consideration.

Write the human note that the metadata cannot supply

A useful note should identify the reference, explain the intended comparison, and state the most important unresolved assumptions. It should call the image an illustrative placement mockup rather than a completed or healed result. Keep physical dimensions and professional decisions outside the validator's output. A consistent metadata record can make a conversation easier, but it cannot supply the reasons an artist or the person considering a design may need to revise or reject it.

A text assistant can help review that note for ambiguous wording without receiving a personal body photo. Ask it to flag claims of completed work, independent testing, or verified scale, then compare its suggestions with the actual materials. Do not let fluent generated language fill missing facts. The relationship between an image and a supplied record remains an assumption unless a separate, appropriately scoped process establishes it.

Decide what should not be stored or published

The safest sample data for a public technical article is synthetic data with an explicit label. A real project should consider whether photo labels, artwork names, filenames, backgrounds, or associated notes identify someone. This example is not a data-retention policy and does not imply consent to share a body image. Do not commit personal images or real reference registers to a public repository simply because the comparison code itself is small.

The resulting tool is deliberately modest: it checks a declaration against structured metadata and reports disagreements. That limited result can still be useful because it is understandable, testable, and easy to discuss. Keep the program's consistency checks, the visual review, the permission process, and any professional judgment separate. Clear boundaries are more valuable than a reassuring validator name that appears to certify things the code never examines.

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?