From 2571627701e961d4f17548a407dffe32e8748134 Mon Sep 17 00:00:00 2001 From: "Brad T. Moore" Date: Fri, 2 Apr 2021 12:05:39 -0400 Subject: [PATCH 1/2] ENH: Added ImageMomentsCalculator test and Jupyter example. --- src/Filtering/ImageStatistics/CMakeLists.txt | 2 + .../CalculateImageMoments/CMakeLists.txt | 37 ++++++ .../CalculateEllipseMoments.ipynb | 114 ++++++++++++++++++ .../CalculateImageMoments/Code.cxx | 56 +++++++++ .../CalculateImageMoments/Code.py | 33 +++++ .../CalculateImageMoments/Documentation.rst | 76 ++++++++++++ .../CalculateImageMoments/ellipse.mha.sha512 | 1 + src/Filtering/ImageStatistics/index.rst | 1 + 8 files changed, 320 insertions(+) create mode 100644 src/Filtering/ImageStatistics/CalculateImageMoments/CMakeLists.txt create mode 100644 src/Filtering/ImageStatistics/CalculateImageMoments/CalculateEllipseMoments.ipynb create mode 100644 src/Filtering/ImageStatistics/CalculateImageMoments/Code.cxx create mode 100755 src/Filtering/ImageStatistics/CalculateImageMoments/Code.py create mode 100644 src/Filtering/ImageStatistics/CalculateImageMoments/Documentation.rst create mode 100644 src/Filtering/ImageStatistics/CalculateImageMoments/ellipse.mha.sha512 diff --git a/src/Filtering/ImageStatistics/CMakeLists.txt b/src/Filtering/ImageStatistics/CMakeLists.txt index a49468b0f..2a5c99dd8 100644 --- a/src/Filtering/ImageStatistics/CMakeLists.txt +++ b/src/Filtering/ImageStatistics/CMakeLists.txt @@ -45,3 +45,5 @@ add_example(ComputeMinMaxVarianceMeanOfImage) #compare_to_baseline(EXAMPLE_NAME StatisticalPropertiesOfRegions # BASELINE_PREFIX OutputBaseline # ) + +add_example(CalculateImageMoments) diff --git a/src/Filtering/ImageStatistics/CalculateImageMoments/CMakeLists.txt b/src/Filtering/ImageStatistics/CalculateImageMoments/CMakeLists.txt new file mode 100644 index 000000000..e0898b246 --- /dev/null +++ b/src/Filtering/ImageStatistics/CalculateImageMoments/CMakeLists.txt @@ -0,0 +1,37 @@ +cmake_minimum_required(VERSION 3.10.2) + +project( CalculateImageMoments ) + +find_package( ITK REQUIRED ) +include( ${ITK_USE_FILE} ) + +add_executable( CalculateImageMoments Code.cxx ) +target_link_libraries( CalculateImageMoments ${ITK_LIBRARIES} ) + +install( TARGETS CalculateImageMoments + DESTINATION bin/ITKExamples/Filtering/ImageStatistics + COMPONENT Runtime +) + +install( FILES Code.cxx CMakeLists.txt Code.py + DESTINATION share/ITKExamples/Code/Filtering/ImageStatistics/CalculateImageMoments + COMPONENT Code +) + +enable_testing() + +set(input_image ${CMAKE_CURRENT_BINARY_DIR}/ellipse.mha) + +add_test( NAME CalculateImageMomentsTest + COMMAND ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/CalculateImageMoments + ${input_image} +) + +if( ITK_WRAP_PYTHON ) + find_package(PythonInterp REQUIRED) + string( REPLACE . "Python." output_image "${output_image}" ) + add_test( NAME CalculateImageMomentsTestPython + COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/Code.py + ${input_image} + ) +endif() diff --git a/src/Filtering/ImageStatistics/CalculateImageMoments/CalculateEllipseMoments.ipynb b/src/Filtering/ImageStatistics/CalculateImageMoments/CalculateEllipseMoments.ipynb new file mode 100644 index 000000000..3d771f7a4 --- /dev/null +++ b/src/Filtering/ImageStatistics/CalculateImageMoments/CalculateEllipseMoments.ipynb @@ -0,0 +1,114 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 9, + "id": "protecting-hobby", + "metadata": {}, + "outputs": [], + "source": [ + "import itk\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "from ipywidgets import interact" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "dried-demographic", + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "12ddf6ab0ad54db19849293d0d20c252", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "interactive(children=(IntSlider(value=30, description='tx'), IntSlider(value=50, description='ty'), IntSlider(…" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "output_size = [100, 100] # size of binary image containing the ellipse\n", + "\n", + "@interact(tx=(0,output_size[0],1), ty=(0,output_size[1],1), a=(5,50,1), b=(5,50,1), theta=(0,2*np.pi,0.1))\n", + "def foo(tx=30, ty=50, a=5, b=10, theta=np.pi/4.0):\n", + " '''\n", + " Creates a binary image of an ellipse and calculates the image moments. Major (purple) and minor (green)\n", + " principal axes are displayed.\n", + " \n", + " Parameters\n", + " ==========\n", + " tx, ty : translation x and y\n", + " a, b : ellipse horizontal and vertical widths before rotation\n", + " theta : angle of rotation (radians)\n", + " '''\n", + " \n", + " # ellipse starts as unit circle, use world transform to define final ellipse\n", + " ellipse = itk.EllipseSpatialObject[2].New()\n", + " ellipse_transform = itk.AffineTransform[itk.D, 2].New()\n", + " ellipse_transform.Scale([a, b])\n", + " ellipse_transform.Rotate2D(theta)\n", + " ellipse_transform.Translate([tx, ty])\n", + " ellipse.SetObjectToWorldTransform(ellipse_transform)\n", + "\n", + " ellipse_img = itk.spatial_object_to_image_filter(input=ellipse, inside_value=1, outside_value=0, size=output_size)\n", + "\n", + " momentor = itk.ImageMomentsCalculator.New(Image=ellipse_img)\n", + " momentor.Compute()\n", + "\n", + " centroid = momentor.GetCenterOfGravity()\n", + " prin_axes = itk.array_from_matrix(momentor.GetPrincipalAxes())\n", + " minor_axes = prin_axes[0]\n", + " major_axes = prin_axes[1]\n", + "\n", + " fig, ax = plt.subplots(figsize=[8,8])\n", + " plt.imshow(ellipse_img, cmap='gray')\n", + " plt.scatter(centroid[0], centroid[1])\n", + "\n", + " minor_pt = centroid + minor_axes*5\n", + " plt.plot([centroid[0], minor_pt[0]], [centroid[1], minor_pt[1]], color='green')\n", + "\n", + " major_pt = centroid + major_axes*5\n", + " plt.plot([centroid[0], major_pt[0]], [centroid[1], major_pt[1]], color='purple')\n", + "\n", + " print(momentor)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "several-label", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/src/Filtering/ImageStatistics/CalculateImageMoments/Code.cxx b/src/Filtering/ImageStatistics/CalculateImageMoments/Code.cxx new file mode 100644 index 000000000..883832eaf --- /dev/null +++ b/src/Filtering/ImageStatistics/CalculateImageMoments/Code.cxx @@ -0,0 +1,56 @@ +/*========================================================================= + * + * Copyright NumFOCUS + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + *=========================================================================*/ + +#include "itkImageFileReader.h" +#include "itkImageFileWriter.h" +#include "itkImageMomentsCalculator.h" + +int +main(int argc, char * argv[]) +{ + if (argc != 2) + { + std::cerr << "Usage: " << std::endl; + std::cerr << argv[0]; + std::cerr << " " << std::endl; + std::cerr << "Prints image moments from unsigned short binary image."; + std::cerr << std::endl; + return EXIT_FAILURE; + } + const char * inputFileName = argv[1]; + + constexpr unsigned int Dimension = 2; + + using PixelType = unsigned short; + using ImageType = itk::Image; + + using ReaderType = itk::ImageFileReader; + ReaderType::Pointer reader = ReaderType::New(); + reader->SetFileName(inputFileName); + reader->Update(); + + // clang-format off + using FilterType = itk::ImageMomentsCalculator; + // clang-format on + FilterType::Pointer filter = FilterType::New(); + filter->SetImage(reader->GetOutput()); + filter->Compute(); + filter->Print(std::cout, 0); + + return EXIT_SUCCESS; +} diff --git a/src/Filtering/ImageStatistics/CalculateImageMoments/Code.py b/src/Filtering/ImageStatistics/CalculateImageMoments/Code.py new file mode 100755 index 000000000..d2a00378d --- /dev/null +++ b/src/Filtering/ImageStatistics/CalculateImageMoments/Code.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python + +# Copyright NumFOCUS +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0.txt +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys +import itk +import argparse + +if len(sys.argv) != 2: + print("Usage: " + sys.argv[0] + "") + print("Prints image moment information from binary image.") + sys.exit(1) + +parser = argparse.ArgumentParser(description="Calculate Image Moments.") +parser.add_argument("input_image") + +args = parser.parse_args() +input_image = itk.imread(args.input_image) +momentor = itk.ImageMomentsCalculator.New(Image=input_image) +momentor.Compute() +print(momentor) diff --git a/src/Filtering/ImageStatistics/CalculateImageMoments/Documentation.rst b/src/Filtering/ImageStatistics/CalculateImageMoments/Documentation.rst new file mode 100644 index 000000000..0f99d64a9 --- /dev/null +++ b/src/Filtering/ImageStatistics/CalculateImageMoments/Documentation.rst @@ -0,0 +1,76 @@ +Calculate Image Moments +================================ + +.. index:: + single: ImageMomentsCalculator + +Synopsis +-------- + + +Calculate image moments from binary images and an interactive ellipse. + + +Results +------- + +.. figure:: ellipse.mha + :scale: 50% + :alt: Input image + + Input Image +:: +Output + ImageMomentsCalculator (000002037138BE90) + RTTI typeinfo: class itk::ImageMomentsCalculator > + Reference Count: 1 + Modified Time: 249 + Debug: Off + Object Name: + Observers: + none + Image: 000002037138FDD0 + Valid: 1 + Zeroth Moment about origin: 153 + First Moment about origin: [30, 50] + Second Moment about origin: 15.0458 -8.8366 + -8.8366 15.0458 + + Center of Gravity: [30, 50] + Second central moments: 15.0458 -8.8366 + -8.8366 15.0458 + + Principal Moments: [950, 3654] + Principal axes: 0.707107 0.707107 + -0.707107 0.707107 +:: + + +Jupyter Notebook +----------------- + +.. image:: https://mybinder.org/badge_logo.svg + :target: https://mybinder.org/v2/gh/InsightSoftwareConsortium/ITKExamples/master?filepath=src%2FFiltering%2FImageStatistics%2FImageMomentsCalculator%2FCalculateEllipseMoments.ipynb + +Code +---- + +C++ +... + +.. literalinclude:: Code.cxx + :language: c++ + :lines: 18- + +Python +...... + +.. literalinclude:: Code.py + :language: python + :lines: 1,16- + + +Classes demonstrated +-------------------- + +.. breathelink:: itk::ImageMomentsCalculator diff --git a/src/Filtering/ImageStatistics/CalculateImageMoments/ellipse.mha.sha512 b/src/Filtering/ImageStatistics/CalculateImageMoments/ellipse.mha.sha512 new file mode 100644 index 000000000..980c5b8c5 --- /dev/null +++ b/src/Filtering/ImageStatistics/CalculateImageMoments/ellipse.mha.sha512 @@ -0,0 +1 @@ +a9dc7abe4d5745de06491d21addb54f561619732d71c98f250447c4fcf2c1444a60badbebaa3d646913e82fff75bfc753e2495c2d672d5bd7f85860fb8f8da0c diff --git a/src/Filtering/ImageStatistics/index.rst b/src/Filtering/ImageStatistics/index.rst index 59953c068..bd8541eb4 100644 --- a/src/Filtering/ImageStatistics/index.rst +++ b/src/Filtering/ImageStatistics/index.rst @@ -9,3 +9,4 @@ ImageStatistics ComputeMinMaxVarianceMeanOfImage/Documentation.rst ComputePCAShapeFromSample/Documentation.rst StatisticalPropertiesOfRegions/Documentation.rst + CalculateImageMoments/Documentation.rst From 22f432f69041fd65ce96137915b1ef4e664213a9 Mon Sep 17 00:00:00 2001 From: "Brad T. Moore" Date: Tue, 6 Apr 2021 13:39:28 -0400 Subject: [PATCH 2/2] ENH: Add CalculateImageMoments and fix cookiecutter and other issues. DOC: Changed cookiecutter to put Python link first DOC: Changed CalculateImageMoments to put Python link first BUG: Removed extraneous lines in CMakeLists refering to output_image ENH: Changed cookiecutter to use argparse for print_usage ENH: Changed CalculateImageMoments to use argparse for print_usage BUG: Fixed IterateRegionWithAccessToIndexWithoutWriteAccess/Code.cxx to use defined region instead of LargestPossibleRegion (Closes #270) Added ellipse.png.sha512 so that the image would render. Fixed whitespace and syntax issues with .rst. --- .../{{cookiecutter.example_name}}/Code.py | 15 ++-- .../Documentation.rst | 13 ++-- .../Code.cxx | 2 +- .../CalculateImageMoments/CMakeLists.txt | 1 - .../CalculateEllipseMoments.ipynb | 24 ++++-- .../CalculateImageMoments/Code.py | 5 -- .../CalculateImageMoments/Documentation.rst | 75 +++++++++++-------- .../CalculateImageMoments/ellipse.png.sha512 | 1 + 8 files changed, 75 insertions(+), 61 deletions(-) create mode 100644 src/Filtering/ImageStatistics/CalculateImageMoments/ellipse.png.sha512 diff --git a/Utilities/CookieCutter/{{cookiecutter.example_name}}/Code.py b/Utilities/CookieCutter/{{cookiecutter.example_name}}/Code.py index 742a98108..eafd0db60 100755 --- a/Utilities/CookieCutter/{{cookiecutter.example_name}}/Code.py +++ b/Utilities/CookieCutter/{{cookiecutter.example_name}}/Code.py @@ -17,18 +17,15 @@ import sys import itk -if len(sys.argv) != 3: - print("Usage: " + sys.argv[0] + " " - "") - sys.exit(1) - -parser = argparse.ArgumentParser(description='{{ cookiecutter.example_title }}.') -parser.add_argument('input_image') -parser.add_argument('output_image') +parser = argparse.ArgumentParser(description="{{ cookiecutter.example_title }}.") +parser.add_argument("input_image") +parser.add_argument("output_image") args = parser.parse_args() input_image = itk.imread(args.input_image) -output_image = itk.itkHelpers.camel_to_snake_case({{ cookiecutter.class_name }})(input_image) +output_image = itk.itkHelpers.camel_to_snake_case({{cookiecutter.class_name}})( + input_image +) itk.imwrite(output_image, args.output_image) diff --git a/Utilities/CookieCutter/{{cookiecutter.example_name}}/Documentation.rst b/Utilities/CookieCutter/{{cookiecutter.example_name}}/Documentation.rst index f0a82fbe1..039e914b4 100644 --- a/Utilities/CookieCutter/{{cookiecutter.example_name}}/Documentation.rst +++ b/Utilities/CookieCutter/{{cookiecutter.example_name}}/Documentation.rst @@ -32,6 +32,13 @@ Results Code ---- +Python +...... + +.. literalinclude:: Code.py + :language: python + :lines: 1,16- + C++ ... @@ -39,12 +46,6 @@ C++ :language: c++ :lines: 18- -Python -...... - -.. literalinclude:: Code.py - :language: python - :lines: 1,16- Classes demonstrated diff --git a/src/Core/Common/IterateRegionWithAccessToIndexWithoutWriteAccess/Code.cxx b/src/Core/Common/IterateRegionWithAccessToIndexWithoutWriteAccess/Code.cxx index 445df5fba..4d479d97f 100644 --- a/src/Core/Common/IterateRegionWithAccessToIndexWithoutWriteAccess/Code.cxx +++ b/src/Core/Common/IterateRegionWithAccessToIndexWithoutWriteAccess/Code.cxx @@ -53,7 +53,7 @@ main(int argc, char * argv[]) region.SetSize(regionSize); region.SetIndex(regionIndex); - itk::ImageRegionConstIteratorWithIndex imageIterator(image, image->GetLargestPossibleRegion()); + itk::ImageRegionConstIteratorWithIndex imageIterator(image, region); while (!imageIterator.IsAtEnd()) { diff --git a/src/Filtering/ImageStatistics/CalculateImageMoments/CMakeLists.txt b/src/Filtering/ImageStatistics/CalculateImageMoments/CMakeLists.txt index e0898b246..513de6d8a 100644 --- a/src/Filtering/ImageStatistics/CalculateImageMoments/CMakeLists.txt +++ b/src/Filtering/ImageStatistics/CalculateImageMoments/CMakeLists.txt @@ -29,7 +29,6 @@ add_test( NAME CalculateImageMomentsTest if( ITK_WRAP_PYTHON ) find_package(PythonInterp REQUIRED) - string( REPLACE . "Python." output_image "${output_image}" ) add_test( NAME CalculateImageMomentsTestPython COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/Code.py ${input_image} diff --git a/src/Filtering/ImageStatistics/CalculateImageMoments/CalculateEllipseMoments.ipynb b/src/Filtering/ImageStatistics/CalculateImageMoments/CalculateEllipseMoments.ipynb index 3d771f7a4..159f11c69 100644 --- a/src/Filtering/ImageStatistics/CalculateImageMoments/CalculateEllipseMoments.ipynb +++ b/src/Filtering/ImageStatistics/CalculateImageMoments/CalculateEllipseMoments.ipynb @@ -1,9 +1,21 @@ { "cells": [ + { + "cell_type": "markdown", + "id": "revolutionary-headquarters", + "metadata": {}, + "source": [ + "# Calculate Image Moments of an Ellipse\n", + "\n", + "Image moments can be used to determine the center of gravity or average pixel location of a binary object as well as the orientation of an object according to its axes of variation.\n", + "\n", + "The interactive plot below demonstrates how to calculate the image moments of an ellipse-defined mask." + ] + }, { "cell_type": "code", - "execution_count": 9, - "id": "protecting-hobby", + "execution_count": 1, + "id": "amino-atlantic", "metadata": {}, "outputs": [], "source": [ @@ -15,14 +27,14 @@ }, { "cell_type": "code", - "execution_count": 32, - "id": "dried-demographic", + "execution_count": 2, + "id": "domestic-jenny", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "12ddf6ab0ad54db19849293d0d20c252", + "model_id": "9460aeb87fea499c90c4e301da10ddce", "version_major": 2, "version_minor": 0 }, @@ -84,7 +96,7 @@ { "cell_type": "code", "execution_count": null, - "id": "several-label", + "id": "seasonal-sandwich", "metadata": {}, "outputs": [], "source": [] diff --git a/src/Filtering/ImageStatistics/CalculateImageMoments/Code.py b/src/Filtering/ImageStatistics/CalculateImageMoments/Code.py index d2a00378d..6c6b5652f 100755 --- a/src/Filtering/ImageStatistics/CalculateImageMoments/Code.py +++ b/src/Filtering/ImageStatistics/CalculateImageMoments/Code.py @@ -18,11 +18,6 @@ import itk import argparse -if len(sys.argv) != 2: - print("Usage: " + sys.argv[0] + "") - print("Prints image moment information from binary image.") - sys.exit(1) - parser = argparse.ArgumentParser(description="Calculate Image Moments.") parser.add_argument("input_image") diff --git a/src/Filtering/ImageStatistics/CalculateImageMoments/Documentation.rst b/src/Filtering/ImageStatistics/CalculateImageMoments/Documentation.rst index 0f99d64a9..733835242 100644 --- a/src/Filtering/ImageStatistics/CalculateImageMoments/Documentation.rst +++ b/src/Filtering/ImageStatistics/CalculateImageMoments/Documentation.rst @@ -1,50 +1,57 @@ +:name: CalculateImageMoments + Calculate Image Moments ================================ .. index:: single: ImageMomentsCalculator +.. toctree:: + :maxdepth: 1 + + CalculateEllipseMoments.ipynb + Synopsis -------- Calculate image moments from binary images and an interactive ellipse. - Results ------- -.. figure:: ellipse.mha - :scale: 50% +Input Image + +.. figure:: ellipse.png :alt: Input image - Input Image -:: -Output - ImageMomentsCalculator (000002037138BE90) - RTTI typeinfo: class itk::ImageMomentsCalculator > - Reference Count: 1 - Modified Time: 249 - Debug: Off - Object Name: - Observers: - none - Image: 000002037138FDD0 - Valid: 1 - Zeroth Moment about origin: 153 - First Moment about origin: [30, 50] - Second Moment about origin: 15.0458 -8.8366 - -8.8366 15.0458 - - Center of Gravity: [30, 50] - Second central moments: 15.0458 -8.8366 - -8.8366 15.0458 - - Principal Moments: [950, 3654] - Principal axes: 0.707107 0.707107 - -0.707107 0.707107 + :: + Output + ImageMomentsCalculator (000002037138BE90) + RTTI typeinfo: class itk::ImageMomentsCalculator > + Reference Count: 1 + Modified Time: 249 + Debug: Off + Object Name: + Observers: + none + Image: 000002037138FDD0 + Valid: 1 + Zeroth Moment about origin: 153 + First Moment about origin: [30, 50] + Second Moment about origin: 15.0458 -8.8366 + -8.8366 15.0458 + + Center of Gravity: [30, 50] + Second central moments: 15.0458 -8.8366 + -8.8366 15.0458 + + Principal Moments: [950, 3654] + Principal axes: 0.707107 0.707107 + -0.707107 0.707107 + Jupyter Notebook ----------------- @@ -55,6 +62,13 @@ Jupyter Notebook Code ---- +Python +...... + +.. literalinclude:: Code.py + :language: python + :lines: 1,16- + C++ ... @@ -62,12 +76,7 @@ C++ :language: c++ :lines: 18- -Python -...... -.. literalinclude:: Code.py - :language: python - :lines: 1,16- Classes demonstrated diff --git a/src/Filtering/ImageStatistics/CalculateImageMoments/ellipse.png.sha512 b/src/Filtering/ImageStatistics/CalculateImageMoments/ellipse.png.sha512 new file mode 100644 index 000000000..9cbe2c237 --- /dev/null +++ b/src/Filtering/ImageStatistics/CalculateImageMoments/ellipse.png.sha512 @@ -0,0 +1 @@ +faafd547a568d258dc916d18058f6dae75ef3fe31e83ee500e71a9a253eb502961e362f18cfd5c5a4e2edab78341184a891016abee4718b286d564c24afedf35