```python:inference/core/workflows/execution_engine/entities/base.py
import numpy as np
from dataclasses import dataclass
from datetime import datetime
from pydantic import BaseModel, Field
from typing import Optional


class VideoMetadata(BaseModel):
    video_identifier: str = Field(
        description="Identifier string for video. To be treated as opaque."
    )
    frame_number: int
    frame_timestamp: datetime = Field(
        description="The timestamp of video frame. When processing video it is suggested that "
        "blocks rely on `fps` and `frame_number`, as real-world time-elapse will not "
        "match time-elapse in video file",
    )
    fps: Optional[float] = Field(
        description="Field represents FPS value (if possible to be retrieved)",
        default=None,
    )
    measured_fps: Optional[float] = Field(
        description="Field represents measured FPS of live stream",
        default=None,
    )
    comes_from_video_file: Optional[bool] = Field(
        description="Field is a flag telling if frame comes from video file or stream - "
        "if not possible to be determined - pass None",
        default=None,
    )


@dataclass(frozen=True)
class OriginCoordinatesSystem:
    left_top_x: int
    left_top_y: int
    origin_width: int
    origin_height: int


@dataclass(frozen=True)
class ImageParentMetadata:
    parent_id: str
    origin_coordinates: Optional[OriginCoordinatesSystem] = None


class WorkflowImageData:

    def __init__(
        self,
        parent_metadata: ImageParentMetadata,
        workflow_root_ancestor_metadata: Optional[ImageParentMetadata] = None,
        image_reference: Optional[str] = None,
        base64_image: Optional[str] = None,
        numpy_image: Optional[np.ndarray] = None,
        video_metadata: Optional[VideoMetadata] = None,
        image_name: Optional[str] = None,
    ):
        if not base64_image and numpy_image is None and not image_reference:
            raise ValueError("Could not initialise empty `WorkflowImageData`.")
        self._parent_metadata = parent_metadata
        self._workflow_root_ancestor_metadata = (
            workflow_root_ancestor_metadata
            if workflow_root_ancestor_metadata
            else self._parent_metadata
        )
        self._image_reference = image_reference
        self._base64_image = base64_image
        self._numpy_image = numpy_image
        self._video_metadata = video_metadata
        self._image_name = image_name

    @classmethod
    def copy_and_replace(
        cls, origin_image_data: "WorkflowImageData", **kwargs
    ) -> "WorkflowImageData":
        """
        Creates new instance of `WorkflowImageData` with updated property.

        Properties are passed by kwargs, supported properties are:
        * parent_metadata
        * workflow_root_ancestor_metadata
        * image_reference
        * base64_image
        * numpy_image
        * video_metadata
        * image_name

        When more than one from ["numpy_image", "base64_image", "image_reference"] args are
        given, they MUST be compliant.
        """
        parent_metadata = origin_image_data._parent_metadata
        workflow_root_ancestor_metadata = (
            origin_image_data._workflow_root_ancestor_metadata
        )
        image_reference = origin_image_data._image_reference
        base64_image = origin_image_data._base64_image
        numpy_image = origin_image_data._numpy_image
        video_metadata = origin_image_data._video_metadata
        image_name = origin_image_data._image_name

        if ("numpy_image" in kwargs) or ("base64_image" in kwargs) or ("image_reference" in kwargs):
            numpy_image = kwargs.get("numpy_image")
            base64_image = kwargs.get("base64_image")
            image_reference = kwargs.get("image_reference")
        if "parent_metadata" in kwargs:
            if workflow_root_ancestor_metadata is parent_metadata:
                workflow_root_ancestor_metadata = kwargs["parent_metadata"]
            parent_metadata = kwargs["parent_metadata"]
        if "workflow_root_ancestor_metadata" in kwargs:
            if parent_metadata is workflow_root_ancestor_metadata:
                parent_metadata = kwargs["workflow_root_ancestor_metadata"]
            workflow_root_ancestor_metadata = kwargs["workflow_root_ancestor_metadata"]
        if "video_metadata" in kwargs:
            video_metadata = kwargs["video_metadata"]
        if "image_name" in kwargs:
            image_name = kwargs["image_name"]
        return cls(
            parent_metadata,
            workflow_root_ancestor_metadata,
            image_reference,
            base64_image,
            numpy_image,
            video_metadata,
            image_name,
        )
```