Skip to content
Snippets Groups Projects
Commit a05d5b53 authored by Hock, Martin's avatar Hock, Martin
Browse files

Merge branch 'fallback' into 'dev'

Fallback engine implementation

See merge request !17
parents b5762ebf ee6e488e
Branches
Tags
2 merge requests!19merge dev for new release v.0.1.2,!17Fallback engine implementation
Pipeline #773681 passed
## Implement a new plot engine
If you want to add another plot engine "$engine" to plotID, this document helps you to do it.
### tagplot
Create a new module named "tagplot_$engine.py". Paste the following code and replace every "$engine" with the name of your engine:
```python
"""
Tag your picture with an ID.
Functions:
tagplot_$engine(PlotOptions instance) -> list
"""
import $engine
from plotid.create_id import create_id
from plotid.plotoptions import PlotOptions
def tagplot_$engine(plotid_object):
"""
Add IDs to plots with $engine.
The ID is placed visual on the figure window and returned as string in a
list together with the figures.
TagPlot can tag multiple figures at once.
"""
# Check if plotid_object is a valid instance of PlotOptions
if not isinstance(plotid_object, PlotOptions):
raise TypeError('The given options container is not an instance'
'of PlotOptions.')
ids_as_list = []
for fig in plotid_object.figs:
figure_id = create_id(plotid_object.id_method)
ids_as_list.append(figure_id)
"""
Insert here the tagging with $engine:
Open the figure fig.
Place the string figure_id on it.
Use plotid_object.position and plotid_object.rotation for position and rotation.
Save the tagged figure to plotid_object.figs.
"""
return [plotid_object.figs, ids_as_list]
Last step:
Add the following code in tagplot.py:
match engine:
[...]
case '$engine':
return tagplot_$engine(option_container)
case _:
[...]
```
### publish
To include a new plot engine in the publish function only save_plot.py has to be touched.
Import the plot engine at the top of the file.
In the beginning of the function save_plot() create the following line:
```python
if isinstance(figures, $type_of_figure):
figures = [figures]
[...]
if not isinstance(figures, list):
raise TypeError('Figures are not given as list.')
```
This allows to iterate through all figures, even if it is only one. It must be placed before the line that checks if figures is a list.
Create a new elif condition in the for loop:
```python
for i, fig in enumerate(figures):
[...]
elif isinstance(fig, $type_of_figure):
# Read/Open the figure fig if necessary
plot_path.append(plot_names[i] + '.' + extension)
# Save the figure fig to plot_path[i]
```
\ No newline at end of file
......@@ -47,7 +47,8 @@ Tag your figure/plot with an ID. It is possible to tag multiple figures at once.
`tagplot(figures, plot_engine)`
The variable "figures" can be a single figure or a list of multiple figures.
The argument "plot_engine" defines which plot engine was used to create the figures. It also determines which plot engine plotID uses to place the ID on the plot. Currently supported plot engines are:
- 'matplotlib'
- 'matplotlib', which processes figures created by matplotlib.
- 'image', which processes pictures with common extensions (jpg, png, etc.)
tagplot returns a list that contains two lists each with as many entries as figures were given. The first list contains the tagged figures. The second list contains the corresponding IDs as strings
......@@ -74,7 +75,7 @@ Save plot, data and measuring script. It is possible to export multiple figures
- "src_datapath" specifies the path to (raw) data that should be published. It can be a string or a list of strings that specifies all files and directories which will be published.
- "dst_path" is the path to the destination directory, where all the data should be copied/exported to.
- "figure" expects the figure or a list of figures that were tagged and now should be saved as pictures.
- "figure" expects the figure or a list of figures that were tagged and now should be saved as pictures. If image files were tagged, all of them need to have the same file extension.
- "plot_names" will be the file names for the exported plots. If you give only one plot name but several figures, plotID will name the exported plots with an appended number, e.g. example_fig1.png, example_fig2.png, ...
Optional parameters can be used to customize the publish process.
......
......@@ -2,14 +2,13 @@
"""
Example workflow for integrating plotID.
With tagplot an ID can be generated an printed on the plot. To export the plot
along with the corresponding research data and the plot generating script use
the function publish.
With tagplot() an ID can be generated and printed on the plot. To export the
plot along with the corresponding research data and the plot generating
script use the function publish().
"""
# %% Import modules
import numpy as np
# import h5py as h5
import matplotlib.pyplot as plt
from plotid.tagplot import tagplot
from plotid.publish import publish
......@@ -17,44 +16,45 @@ from plotid.publish import publish
# %% Set Project ID
PROJECT_ID = "MR04_"
# %% Choose Plot engine
PLOT_ENGINE = "matplotlib"
# %% Create sample data
x = np.linspace(0, 10, 100)
y = np.random.rand(100) + 2
y_2 = np.sin(x) + 2
# %% Create figures
# %% Create sample figures
# 1. figure
IMG1 = 'image1.png'
FIG1 = plt.figure()
plt.plot(x, y, color='black')
plt.plot(x, y_2, color='yellow')
plt.savefig(IMG1)
# 2. figure
IMG2 = 'image2.png'
FIG2 = plt.figure()
plt.plot(x, y, color='blue')
plt.plot(x, y_2, color='red')
plt.savefig(IMG2)
# %% TagPlot
# If multiple figures should be tagged, figures must be provided as list.
FIGS_AS_LIST = [FIG1, FIG2]
IMGS_AS_LIST = [IMG1, IMG2]
[TAGGED_FIGS, ID] = tagplot(FIGS_AS_LIST, PLOT_ENGINE, prefix=PROJECT_ID,
id_method='random', location='west')
# Example for how to use tagplot with matplotlib figures
[TAGGED_FIGS, ID] = tagplot('FIGS_AS_LIST', 'matplotlib', prefix=PROJECT_ID,
id_method='time', location='west')
# %% Save figure as tiff-file, but publish also exports the plot to a picture
# file in the destination folder.
# for i, figure in enumerate(TAGGED_FIGS):
# NAME = "Test"+str(i)+".tiff"
# figure.savefig(NAME)
# Example for how to use tagplot with image files
# [TAGGED_FIGS, ID] = tagplot(IMGS_AS_LIST, 'image', prefix=PROJECT_ID,
# id_method='random', location='west')
# %% Publish
# Arguments: Source directory, destination directory, figure, plot name,
# publish-mode).
# Arguments: Source directory or files as list, destination directory, figures,
# plots or images.
publish(['../README.md', '../tests', '../LICENSE'],
publish(['../README.md', '../dist', '../LICENSE'],
'/home/chief/Dokumente/fst/plotid_python/data',
FIGS_AS_LIST, 'Bild', 'individual')
TAGGED_FIGS, 'Bild')
......@@ -41,7 +41,7 @@ class PlotOptions:
Raises
------
TypeError
TypeError is thrown if one of the attributes is not the correct
TypeError is thrown if one of the attributes is not of correct
type.
Returns
......@@ -50,14 +50,14 @@ class PlotOptions:
"""
# Input validation for figs is done in submodules tagplot_$engine.py
if isinstance(self.prefix, str):
pass
else:
if not isinstance(self.prefix, str):
raise TypeError("Prefix is not a string.")
if isinstance(self.id_method, str):
pass
else:
if not isinstance(self.id_method, str):
raise TypeError('The chosen id_method is not a string.')
# Store figs in a list, even if it is only one.
if not isinstance(self.figs, list):
self.figs = [self.figs]
return 0
......@@ -46,7 +46,7 @@ class PublishOptions:
"""
def __init__(self, src_datapaths, dst_path, figure, plot_names,
data_storage):
data_storage='individual'):
self.src_datapaths = src_datapaths
self.dst_path = dst_path
......@@ -167,6 +167,8 @@ class PublishOptions:
def centralized_data_storage(self):
"""
[not implemented yet].
Store the data only in one directory and link all others to it.
Returns
......@@ -175,7 +177,6 @@ class PublishOptions:
"""
# Does nothing, not implemented yet
pass
def individual_data_storage(self, destination, pic_paths):
"""
......
......@@ -34,13 +34,14 @@ def save_plot(figures, plot_names, extension='png'):
# Check if figs is a valid figure or a list of valid figures
if isinstance(figures, matplotlib.figure.Figure):
figures = [figures]
if isinstance(figures, list):
for fig in figures:
if isinstance(fig, matplotlib.figure.Figure):
pass
else:
raise TypeError('Figures is neither a valid matplotlib-figure nor'
' a list of matplotlib-figures.')
# PIL has different classes for different file formats. Therefore, the
# type is checked to contain 'PIL' and 'ImageFile'.
if all(x in str(type(figures)) for x in ['PIL', 'ImageFile']):
figures = [figures]
if not isinstance(figures, list):
raise TypeError('Figures are not given as list.')
if isinstance(plot_names, str):
plot_names = [plot_names]
if len(plot_names) < len(figures):
warnings.warn('There are more figures than plot names. The first name'
......@@ -53,13 +54,16 @@ def save_plot(figures, plot_names, extension='png'):
raise IndexError('There are more plot names than figures.')
plot_path = []
# match type(figures):
# case matplotlib.figure.Figure:
for i, fig in enumerate(figures):
if isinstance(fig, matplotlib.figure.Figure):
plt.figure(fig)
plot_path.append(plot_names[i] + '.' + extension)
plt.savefig(plot_path[i])
# case _:
# raise TypeError('The given figure is not a valid figure object.')
elif all(x in str(type(fig)) for x in ['PIL', 'ImageFile']):
plot_path.append(plot_names[i] + '.' + extension)
fig.save(plot_path[i])
else:
raise TypeError(f'Figure number {i} is not a valid figure object.')
return plot_path
......@@ -7,12 +7,13 @@ For publishing the tagged plot along your research data have a look at the
module publish.
Functions:
TagPlot(figure object, string) -> list
tagplot(figure object, string) -> list
"""
import warnings
from plotid.plotoptions import PlotOptions
from plotid.tagplot_matplotlib import tagplot_matplotlib
from plotid.tagplot_image import tagplot_image
def tagplot(figs, engine, prefix='', id_method='time', location='east'):
......@@ -88,6 +89,8 @@ def tagplot(figs, engine, prefix='', id_method='time', location='east'):
match engine:
case 'matplotlib' | 'pyplot':
return tagplot_matplotlib(option_container)
case 'image' | 'fallback':
return tagplot_image(option_container)
case _:
raise ValueError(
f'The plot engine "{engine}" is not supported.')
# -*- coding: utf-8 -*-
"""
Tag your picture with an ID.
Functions:
tagplot_image(PlotOptions instance) -> list
"""
import os
from PIL import Image, ImageDraw, ImageFont, ImageOps
from plotid.create_id import create_id
from plotid.plotoptions import PlotOptions
def tagplot_image(plotid_object):
"""
Add IDs to images/pictures with pillow.
The ID is placed visual on the figure window and returned as string in a
list together with the figures.
TagPlot can tag multiple figures at once.
"""
# Check if plotid_object is a valid instance of PlotOptions
if not isinstance(plotid_object, PlotOptions):
raise TypeError('The given options container is not an instance'
'of PlotOptions.')
# Check if figs is a list of files
for img in plotid_object.figs:
if not isinstance(img, str):
raise TypeError('Name of the image is not a string.')
if not os.path.isfile(img):
raise TypeError('File does not exist.')
# Check if figs is a valid file is done by pillow internally
ids_as_list = []
color = (128, 128, 128) # grey
font = ImageFont.load_default()
for i, img in enumerate(plotid_object.figs):
img_id = plotid_object.prefix + create_id(plotid_object.id_method)
ids_as_list.append(img_id)
img = Image.open(img)
img_txt = Image.new('L', font.getsize(img_id))
draw_txt = ImageDraw.Draw(img_txt)
draw_txt.text((0, 0), img_id, font=font, fill=255)
txt = img_txt.rotate(plotid_object.rotation, expand=1)
img.paste(ImageOps.colorize(txt, (0, 0, 0), color),
(int(img.width*plotid_object.position[0]),
int(img.height*(1-plotid_object.position[1]))), txt)
plotid_object.figs[i] = img
return [plotid_object.figs, ids_as_list]
......@@ -3,7 +3,7 @@
Tag your matplotlib plot with an ID.
Functions:
tagplot_matplotlib(figure object, string) -> list
tagplot_matplotlib(PlotOptions instance) -> list
"""
import matplotlib
......@@ -24,16 +24,11 @@ def tagplot_matplotlib(plotid_object):
if not isinstance(plotid_object, PlotOptions):
raise TypeError('The given options container is not an instance'
'of PlotOptions.')
# Check if figs is a valid figure or a list of valid figures
if isinstance(plotid_object.figs, matplotlib.figure.Figure):
plotid_object.figs = [plotid_object.figs]
if isinstance(plotid_object.figs, list):
# Check if figs is a list of valid figures
for figure in plotid_object.figs:
if isinstance(figure, matplotlib.figure.Figure):
pass
else:
raise TypeError('Figures is neither a valid matplotlib-figure nor'
' a list of matplotlib-figures.')
if not isinstance(figure, matplotlib.figure.Figure):
raise TypeError('Figure is not a valid matplotlib-figure.')
fontsize = 'small'
color = 'grey'
......@@ -45,7 +40,7 @@ def tagplot_matplotlib(plotid_object):
figure_id = plotid_object.prefix + str(figure_id)
ids_as_list.append(figure_id)
plt.figure(fig.number)
plt.figure(fig)
plt.figtext(x=plotid_object.position[0], y=plotid_object.position[1],
s=figure_id, ha='left', wrap=True,
rotation=plotid_object.rotation,
......
......@@ -2,7 +2,7 @@ coverage==6.3.2
cycler==0.11.0
fonttools==4.32.0
kiwisolver==1.4.2
matplotlib==3.5.1
matplotlib==3.5.2
numpy==1.22.3
packaging==21.3
Pillow==9.1.0
......
......@@ -12,6 +12,8 @@ url = https://git.rwth-aachen.de/plotid/plotid_python
project_urls =
Bug Tracker = https://git.rwth-aachen.de/plotid/plotid_python/-/issues
classifiers =
Programming Language :: Python
Programming Language :: Python :: 3 :: Only
Programming Language :: Python :: 3.10
License :: OSI Approved :: Apache Software License
Operating System :: OS Independent
......@@ -23,6 +25,7 @@ classifiers =
packages = plotid
python_requires = >=3.10
install_requires =
Pillow
matplotlib
numpy
......
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Unittests for CreateID
Unittests for create_id
"""
import unittest
......
......@@ -7,10 +7,13 @@ Unittests for save_plot
import os
import unittest
import matplotlib.pyplot as plt
from PIL import Image
from plotid.save_plot import save_plot
FIGURE = plt.figure()
PLOT_NAME = 'PLOT_NAME'
IMG1 = 'image1.png'
IMG2 = 'image2.jpg'
class TestSavePlot(unittest.TestCase):
......@@ -18,12 +21,41 @@ class TestSavePlot(unittest.TestCase):
Class for all unittests of the save_plot module.
"""
def test_save_plot(self):
""" Test if save_plot succeeds with valid arguments. """
def setUp(self):
plt.savefig(IMG1)
plt.savefig(IMG2)
def test_save_plot_matplotlib(self):
"""
Test if save_plot succeeds with valid arguments
using the matplotlib engine.
"""
plot_paths = save_plot(FIGURE, [PLOT_NAME], extension='jpg')
self.assertIsInstance(plot_paths, list)
os.remove(PLOT_NAME + '.jpg')
def test_save_plot_image_png(self):
"""
Test if save_plot succeeds with valid arguments using the image engine
and a png file.
"""
img1 = Image.open(IMG1)
plot_paths = save_plot(img1, [PLOT_NAME])
self.assertIsInstance(plot_paths, list)
os.remove(PLOT_NAME + '.png')
def test_save_plot_image_jpg(self):
"""
Test if save_plot succeeds with valid arguments using the image engine
and a list of jpg files.
"""
img2 = Image.open(IMG2)
imgs_as_list = [img2, img2]
plot_paths = save_plot(imgs_as_list, [PLOT_NAME], extension='jpg')
self.assertIsInstance(plot_paths, list)
os.remove(PLOT_NAME + '1.jpg')
os.remove(PLOT_NAME + '2.jpg')
def test_more_figs_than_names(self):
"""
Test if a warning is raised if more figures than plot names are given.
......@@ -45,6 +77,18 @@ class TestSavePlot(unittest.TestCase):
with self.assertRaises(TypeError):
save_plot('figure', 'PLOT_NAME', extension='jpg')
def test_wrong_fig_in_list(self):
"""
Test if Error is raised when one figure is invalid in a list of
valid figures.
"""
with self.assertRaises(TypeError):
save_plot([FIGURE, 'figure', FIGURE], 'PLOT_NAME', extension='jpg')
def tearDown(self):
os.remove(IMG1)
os.remove(IMG2)
if __name__ == '__main__':
unittest.main()
......@@ -4,6 +4,7 @@
Unittests for tagplot
"""
import os
import unittest
import matplotlib.pyplot as plt
from plotid.tagplot import tagplot
......@@ -12,6 +13,9 @@ from plotid.tagplot import tagplot
FIG1 = plt.figure()
FIG2 = plt.figure()
FIGS_AS_LIST = [FIG1, FIG2]
IMG1 = 'image1.png'
IMG2 = 'image2.jpg'
IMGS_AS_LIST = [IMG1, IMG2]
PROJECT_ID = "MR01"
PLOT_ENGINE = "matplotlib"
......@@ -23,6 +27,17 @@ class TestTagplot(unittest.TestCase):
Class for all unittests of the tagplot module.
"""
def setUp(self):
plt.savefig(IMG1)
plt.savefig(IMG2)
def test_tagplot(self):
"""
Test if tagplot runs successful.
"""
tagplot(FIGS_AS_LIST, PLOT_ENGINE, PROJECT_ID, METHOD)
tagplot(IMGS_AS_LIST, 'image', PROJECT_ID, METHOD, location='north')
def test_prefix(self):
""" Test if Error is raised if prefix is not a string. """
with self.assertRaises(TypeError):
......@@ -59,6 +74,10 @@ class TestTagplot(unittest.TestCase):
tagplot(FIGS_AS_LIST, PLOT_ENGINE, PROJECT_ID, METHOD,
location='up')
def tearDown(self):
os.remove(IMG1)
os.remove(IMG2)
if __name__ == '__main__':
unittest.main()
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Unittests for tagplot_image
"""
import os
import unittest
import matplotlib.pyplot as plt
from PIL import PngImagePlugin, JpegImagePlugin
from plotid.tagplot_image import tagplot_image
from plotid.plotoptions import PlotOptions
FIG1 = plt.figure()
IMG1 = 'image1.png'
FIG2 = plt.figure()
IMG2 = 'image2.jpg'
IMGS_AS_LIST = [IMG1, IMG2]
# Constants for tests
PROJECT_ID = "MR01"
METHOD = 'time'
ROTATION = 90
POSITION = (0.975, 0.35)
class TestTagplotImage(unittest.TestCase):
"""
Class for all unittests of the tagplot_image module.
"""
def setUp(self):
plt.savefig(IMG1)
plt.savefig(IMG2)
def test_images(self):
"""
Test of returned objects. Check if they are png and jpg files,
respectively.
"""
options = PlotOptions(IMGS_AS_LIST, PROJECT_ID, METHOD,
ROTATION, POSITION)
options.validate_input()
[figs, _] = tagplot_image(options)
self.assertIsInstance(figs[0], PngImagePlugin.PngImageFile)
self.assertIsInstance(figs[1], JpegImagePlugin.JpegImageFile)
def test_single_image(self):
"""
Test of returned objects. Check if png files are returned,
if a single matplot figure is given (not as a list).
"""
options = PlotOptions(IMG1, PROJECT_ID, METHOD, ROTATION,
POSITION)
options.validate_input()
[figs, _] = tagplot_image(options)
self.assertIsInstance(figs[0], PngImagePlugin.PngImageFile)
def test_image_not_str(self):
""" Test if Error is raised if wrong type of image is given. """
options = PlotOptions(3, PROJECT_ID, METHOD, ROTATION,
POSITION)
options.validate_input()
with self.assertRaises(TypeError):
tagplot_image(options)
def test_image_not_file(self):
""" Test if Error is raised if the image file does not exist. """
options = PlotOptions('xyz', PROJECT_ID, METHOD, ROTATION,
POSITION)
options.validate_input()
with self.assertRaises(TypeError):
tagplot_image(options)
def test_image_plotoptions(self):
"""
Test if Error is raised if not an instance of PlotOptions is passed.
"""
with self.assertRaises(TypeError):
tagplot_image('wrong_object')
def tearDown(self):
os.remove(IMG1)
os.remove(IMG2)
if __name__ == '__main__':
unittest.main()
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Unittests for TagPlot_matplotlib
Unittests for tagplot_matplotlib
"""
import unittest
......@@ -29,8 +29,9 @@ class TestTagplotMatplotlib(unittest.TestCase):
def test_mplfigures(self):
""" Test of returned objects. Check if they are matplotlib figures. """
options = PlotOptions(FIGS_AS_LIST, PROJECT_ID, METHOD, ROTATION,
POSITION)
options = PlotOptions(FIGS_AS_LIST, PROJECT_ID, METHOD,
ROTATION, POSITION)
options.validate_input()
[figs, _] = tagplot_matplotlib(options)
self.assertIsInstance(figs[0], Figure)
self.assertIsInstance(figs[1], Figure)
......@@ -40,13 +41,17 @@ class TestTagplotMatplotlib(unittest.TestCase):
Test of returned objects. Check if matplotlib figures are returned,
if a single matplot figure is given (not as a list).
"""
options = PlotOptions(FIG1, PROJECT_ID, METHOD, ROTATION, POSITION)
options = PlotOptions(FIG1, PROJECT_ID, METHOD, ROTATION,
POSITION)
options.validate_input()
[figs, _] = tagplot_matplotlib(options)
self.assertIsInstance(figs[0], Figure)
def test_mplerror(self):
""" Test if Error is raised if wrong type of figures is given. """
options = PlotOptions(3, PROJECT_ID, METHOD, ROTATION, POSITION)
options = PlotOptions(3, PROJECT_ID, METHOD, ROTATION,
POSITION)
options.validate_input()
with self.assertRaises(TypeError):
tagplot_matplotlib(options)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment