PreProcessImage

This script pre-processes (resizes, sharpens, and grayscales) an image file from context, given an entry_id.

python · Common Scripts

Details

IDPreProcessImage
Languagepython
From Version6.9.0
Docker Imagedemisto/processing-image-file:1.0.0.11193239

README

This script pre-processes an image file by preforming one of the following actions:

  • original - return the original image (the default action).
  • sharpen - process that emphasizes the edges in an image to make it appear clearer and more focused.
  • grayscale - convert the image to black, white, and gray colors, in which gray has multiple levels (the value of each pixel represents only the intensity information of the light).

Each action can also resize the image when given the width and height.

Script Data


Name Description
Script Type python3
Cortex XSOAR Version 6.8.0

Inputs


Argument Name Description
action The action to perform on the image.
image_resize_width The desired width for resizing the image to.
image_resize_height The desired height for resizing the image to.
file_entry_id The entryID of the file to process.

Outputs


Path Description Type
PreProcessImage.file_entry_id_new The entryID of the created file. String
PreProcessImage.action The action that was performed. String

Script Examples

Example command

!PreProcessImage action=sharpened file_entry_id=<file_entry_id_example> image_resize_height=1700

Context Example

{
    "File": {
        "EntryID": "<EntryIDExample>",
        "Info": "image/jpeg",
        "MD5": "<MD5Example>",
        "Name": "sharpened_IMG_3092",
        "SHA1": "<SHA1Example>",
        "SHA256": "<SHA256Example>",
        "SHA512": "<SHA512Example>",
        "SSDeep": "24576:<SSDeepExample>",
        "Size": 795730,
        "Type": "JPEG image data, JFIF standard 1.01, aspect ratio, density 1x1, segment length 16, baseline, precision 8, 3024x1700, components 3"
    }
}

Example command

!PreProcessImage action=original file_entry_id=<file_entry_id_example> image_resize_height=1700 image_resize_width=500

Context Example

{
    "File": {
        "EntryID": "<EntryIDExample>",
        "Info": "image/jpeg",
        "MD5": "<MD5Example>",
        "Name": "original_IMG_3092",
        "SHA1": "<SHA1Example>",
        "SHA256": "<SHA256Example>",
        "SHA512": "<SHA512Example>",
        "SSDeep": "3072:<SSDeepExample>",
        "Size": 186613,
        "Type": "JPEG image data, JFIF standard 1.01, aspect ratio, density 1x1, segment length 16, baseline, precision 8, 500x1700, components 3"
    }
}
import io

import cv2
import pytest
from CommonServerPython import *
from PIL import Image

IMAGE_NAME_JPG = "test_picture_jpg.jpg"
IMAGE_PATH_JPG = "test_data/test_picture_jpg.jpg"
IMAGE_NAME_PNG = "test_picture_png.png"
IMAGE_PATH_PNG = "test_data/test_picture_png.png"


IMAGE_DETAILS_CASES = [
    (
        IMAGE_PATH_JPG,
        50,
        70,
        {"format": "JPEG", "sizes": (50, 70)},  # expected
    ),
    (
        IMAGE_PATH_JPG,
        None,
        None,
        {"format": "JPEG", "sizes": None},  # expected
    ),
    (
        IMAGE_PATH_PNG,
        50,
        70,
        {"format": "PNG", "sizes": (50, 70)},  # expected
    ),
    (
        IMAGE_PATH_PNG,
        None,
        None,
        {"format": "PNG", "sizes": None},  # expected
    ),
]


@pytest.mark.parametrize("path, width, height, expected_results", IMAGE_DETAILS_CASES)
def test_get_image_details(path, width, height, expected_results):
    """
    Given:
        - mocker
    When:
        - Calling function get_image_details
    Then:
        - Validate image details returned as expected
    """
    from PreProcessImage import get_image_details

    format_, sizes = get_image_details(path, width, height)
    assert format_ == expected_results["format"]
    assert sizes == expected_results["sizes"]


FILES_DETAILS_CASES = [(1, IMAGE_PATH_JPG, IMAGE_NAME_JPG, 50, 70, "JPEG"), (1, IMAGE_PATH_PNG, IMAGE_NAME_PNG, 50, 70, "PNG")]


@pytest.mark.parametrize("entry_id, path, name, width, height, format_img", FILES_DETAILS_CASES)
def test_get_file_details(mocker, entry_id, path, name, width, height, format_img):
    """
    Given:
        - mocker
    When:
        - Calling function get_file_details
    Then:
        - Validate file details returned as expected
    """
    from unittest import mock

    from PreProcessImage import get_file_details

    def mock_file(_id):
        return {
            "path": path,
            "name": name,
        }

    mocker.patch.object(demisto, "getFilePath", side_effect=mock_file)
    mock_image_details = mocker.patch("PreProcessImage.get_image_details", return_value=(format_img, (width, height)))
    # Create a mock for Image.fromarray()
    mock_fromarray = mocker.patch("PIL.Image.fromarray")
    mock_final_orig_image = mock.MagicMock()
    mock_fromarray.return_value = mock_final_orig_image
    mocker.patch("os.path.splitext", return_value=(name, format_img))
    result_img, result_name, result_img_sizes, result_img_format = get_file_details(entry_id, width, height)
    assert mock_image_details.call_count == 1
    assert result_img_sizes == (width, height)
    assert result_name == name
    assert result_img_format == format_img
    assert result_img.any()
    with Image.open(path) as origin_image:
        expected_width, expected_height = origin_image.size
    assert result_img.shape == (expected_height, expected_width, 3)


ACTION_WRAP_CASES = [
    (IMAGE_PATH_JPG, IMAGE_NAME_JPG, "JPEG"),
    (IMAGE_PATH_JPG, IMAGE_NAME_JPG, "JPEG"),
    (IMAGE_PATH_JPG, IMAGE_NAME_JPG, "JPEG"),
    (IMAGE_PATH_PNG, IMAGE_NAME_PNG, "PNG"),
    (IMAGE_PATH_PNG, IMAGE_NAME_PNG, "PNG"),
    (IMAGE_PATH_PNG, IMAGE_NAME_PNG, "PNG"),
]


@pytest.mark.parametrize("image_path, image_name, format_img", ACTION_WRAP_CASES)
def test_grayscale(mocker, image_path, image_name, format_img):
    """
    Given:
    - An image in np.ndarray format.
    - An action to convert the image to grayscale.
    When:
    - Calling the action_wrap function.
    Then:
    - Ensure the function successfully converts the image to grayscale.
    """
    import PreProcessImage
    from PreProcessImage import action_wrap

    # Arrange
    args = {"action": "grayscale", "file_entry_id": "1", "image_resize_width": None, "image_resize_height": None}
    from unittest import mock

    mocker.patch.object(demisto, "getFilePath", return_value={"path": image_path, "name": image_name})
    mocker.patch.object(Image.Image, "resize")
    mocker.patch.object(io, "BytesIO")
    mocker.patch.object(cv2, "cvtColor")
    # Create a mock for Image.fromarray()
    mock_fromarray = mocker.patch("PIL.Image.fromarray")
    mock_final_orig_image = mock.MagicMock()
    mock_fromarray.return_value = mock_final_orig_image
    # Create a mock for Image.fromarray()
    mock_fromarray = mocker.patch("PIL.Image.fromarray")
    mock_final_orig_image = mock.MagicMock()
    mock_fromarray.return_value = mock_final_orig_image

    # Create a mock for io.BytesIO()
    mock_stream_orig = mock.MagicMock()
    # Create a mock for save() and seek() methods
    mocker.patch.object(mock_stream_orig, "save")
    mocker.patch.object(mock_stream_orig, "seek")
    mocker.patch.object(PreProcessImage, "fileResult", return_value=image_path)
    # Act
    action_wrap(args)
    assert cv2.cvtColor.call_args[0][1] == cv2.COLOR_BGR2GRAY
    assert Image.fromarray.call_args[0][0] == cv2.cvtColor.return_value
    assert mock_fromarray.call_count == 1


@pytest.mark.parametrize("image_path, image_name, format_img", ACTION_WRAP_CASES)
def test_sharpened(mocker, image_path, image_name, format_img):
    """
    Given:
    - An image in np.ndarray format.
    - An action to sharpen the image.
    When:
    - Calling the action_wrap function.
    Then:
    - Ensure the function successfully sharpens the image.
    """
    import io

    import PreProcessImage

    # Arrange
    args = {"action": "sharpened", "file_entry_id": "1", "image_resize_width": None, "image_resize_height": None}
    mocker.patch.object(demisto, "getFilePath", return_value={"path": image_path, "name": image_name})
    mocker.patch.object(Image, "fromarray")
    mocker.patch.object(Image.Image, "resize")
    # mocker.patch.object(Image.Image, 'save')
    mocker.patch.object(io, "BytesIO")
    mocker.patch.object(cv2, "filter2D")

    def mock_file(filename, data, file_type=None):
        return {
            "Contents": "",
            "ContentsFormat": formats["text"],
            "Type": entryTypes["file"],
            "File": f"sharpened_{image_name}",
            "FileID": "1",
        }

    mocker.patch.object(PreProcessImage, "fileResult", side_effect=mock_file)
    # Act
    result = PreProcessImage.action_wrap(args)
    # Assert
    assert result["Type"] == entryTypes["file"]
    assert result["File"].startswith("sharpened_")
    assert cv2.filter2D.call_args[0][0].any()
    assert cv2.filter2D.call_args[0][1] == -1
    assert cv2.filter2D.call_args[0][2].tolist()
    assert Image.fromarray.call_args[0][0].any()
    assert Image.Image.resize.call_count == 0


@pytest.mark.parametrize("image_path, image_name, format_img", ACTION_WRAP_CASES)
def test_original(mocker, image_path, image_name, format_img):
    """
    Given:
    - An image in np.ndarray format.
    - An action to return the original image.
    When:
    - Calling the action_wrap function.
    Then:
    - Ensure the function successfully returns the original image (and not preforms sharpening or grayscale).
    """
    from unittest import mock

    import PreProcessImage

    # Arrange
    args = {"action": "original", "file_entry_id": "1", "image_resize_width": 50, "image_resize_height": 50}
    mocker.patch.object(demisto, "getFilePath", return_value={"path": image_path, "name": image_name})
    mocker.patch.object(Image, "fromarray")
    mocker.patch.object(Image.Image, "resize")
    mocker.patch.object(Image.Image, "save")
    mocker.patch.object(io, "BytesIO")
    # Create a mock for image_resize()
    mock_image_sharpened = mocker.patch("PreProcessImage.sharpened")
    mock_sharpened_orig_image = mock.MagicMock()
    mock_image_sharpened.return_value = mock_sharpened_orig_image

    def mock_file(filename, data, file_type=None):
        return {
            "Contents": "",
            "ContentsFormat": formats["text"],
            "Type": entryTypes["file"],
            "File": f"original_{image_name}",
            "FileID": "1",
        }

    mocker.patch.object(PreProcessImage, "fileResult", side_effect=mock_file)
    # Act
    result = PreProcessImage.action_wrap(args)
    # Assert
    assert result["Type"] == entryTypes["file"]
    assert result["File"].startswith("original_")
    assert Image.fromarray.call_args[0][0].any()
    assert mock_image_sharpened.call_count == 0
    assert Image.Image.resize.call_count == 0


def test_invalid_action(mocker):
    """
    Given:
    - An invalid action.
    When:
    - Calling the action_wrap function.
    Then:
    - Ensure the function raises a DemistoException when an invalid action is provided.
    """
    # Arrange
    from PreProcessImage import action_wrap

    args = {"action": "invalid_action", "file_entry_id": "1", "image_resize_width": None, "image_resize_height": None}
    mocker.patch.object(demisto, "getFilePath", return_value={"path": IMAGE_PATH_JPG, "name": IMAGE_NAME_JPG})
    # Act & Assert
    with pytest.raises(DemistoException):
        action_wrap(args)