Skip to content

Commit

Permalink
Test on ubuntu 20.04 and describe how to get python support in readme
Browse files Browse the repository at this point in the history
  • Loading branch information
mathiasburger committed Oct 3, 2020
1 parent a5691d1 commit d2eb0d1
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 49 deletions.
31 changes: 18 additions & 13 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Licensing and DCO

Source files must start with a license header including clarification on the copyright holders and an
Source files must start with a license header including clarification on the copyright holders and an
spdx license identifier. Example:

```
Expand All @@ -14,28 +14,28 @@ spdx license identifier. Example:
By contributing to the project you agree with the [Developer Certificate of Origin](DCO.md).

### DCO Sign-Off Methods

The DCO requires a sign-off message in the following format appear on each commit in the pull request:

> Signed-off-by: Random J Developer <random@developer.example.org>
using your real name (sorry, no pseudonyms or anonymous contributions.)
The DCO text can either be manually added to your commit body, or you can add either **-s** or **--signoff** to
your usual git commit commands. If you forget to add the sign-off you can also amend a previous commit with the
sign-off by running **git commit --amend -s**. If you've pushed your changes to Github already you'll need to

The DCO text can either be manually added to your commit body, or you can add either **-s** or **--signoff** to
your usual git commit commands. If you forget to add the sign-off you can also amend a previous commit with the
sign-off by running **git commit --amend -s**. If you've pushed your changes to Github already you'll need to
force push your branch after this with **git push -f**.

## Project layout and style

A code file is always accompanied by a test file. For classes it will be named `*Test.py`, for modules
A code file is always accompanied by a test file. For classes it will be named `*Test.py`, for modules
`*_Test.py`.

There must not be any todos in the code. Please open an issue or write an entry in [TODO.md](TODO.md).

## Testing

New functionality needs to be tested. The project is using pytest. Test environment configurations can be found
New functionality needs to be tested. The project is using pytest. Test environment configurations can be found
in `docker-compose.yml`. You may configure your IDE to use specific docker-compose services to run the tests.

### Build a specific container
Expand All @@ -54,15 +54,20 @@ docker run --rm -it <CONTAINER_ID> /bin/bash

### Run tests in a specific container

Ubuntu
```
docker-compose run --rm pgimp-ubuntu-20.04 python3 -m pytest -c pytest.ini --maxfail=2 pgimp
```

CentOS
```
docker-compose run --rm pgimp-centos-7.4 scl enable rh-python36 'python3 -m pytest -c pytest.ini --maxfail=2 pgimp'
```

## Documentation

Classes and methods that make up the public API need to be documented. The sphinx documentation root is located
under `doc/`. Docstrings in python are used for documenting entities in the code. Each public method of the API
Classes and methods that make up the public API need to be documented. The sphinx documentation root is located
under `doc/`. Docstrings in python are used for documenting entities in the code. Each public method of the API
should also have a minimum working example.

## Quality checks
Expand All @@ -81,7 +86,7 @@ Is done by the maintainer using the following guidelines:
* [Conda](https://conda.io/docs/user-guide/tutorials/build-pkgs.html)
* [Travis CI Publishing to PyPI](https://docs.travis-ci.com/user/deployment/pypi/)

Tags are created by the maintainer using `tag.sh`. They will be automatically published to the python packaging
Tags are created by the maintainer using `tag.sh`. They will be automatically published to the python packaging
index PyPI using the travis ci when the build of a tag passes.

Examples for tags (the script will prepend a `v` to the given name to indicate that it is a version tag):
Expand Down
25 changes: 15 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,40 +8,45 @@ Interacting with gimp in python3.
Use Cases:
* Autocompletion for writing gimp scripts.
* Batch creation or update of gimp files or data extraction from gimp files.
* Workflows where machine learning data has to be annotated. Raw data
can be converted to gimp files where the annotation process can happen (gimp's thresholding tools
etc. make it easy to do annotation for pixelwise segmentation). After the masks are created, they
* Workflows where machine learning data has to be annotated. Raw data
can be converted to gimp files where the annotation process can happen (gimp's thresholding tools
etc. make it easy to do annotation for pixelwise segmentation). After the masks are created, they
can be converted back to e.g. numpy files.

Read the [documentation](https://pgimp.readthedocs.io/en/latest/) for details on what pgimp can
Read the [documentation](https://pgimp.readthedocs.io/en/latest/) for details on what pgimp can
do for you and how it is achieved. Every single public method comes with a short working example!

## Skeletons for autocompleting gimp scripts

On setup, the `GimpDocumentationGenerator` will generate python skeletons for the methods that gimp
On setup, the `GimpDocumentationGenerator` will generate python skeletons for the methods that gimp
exposes to the interpreter through the procedural database (pdb). This enables autocompletion in your IDE.

## Run a python script within gimp

Running python code within gimp is performed by the `GimpScriptRunner`. Have a look at the corresponding test
Running python code within gimp is performed by the `GimpScriptRunner`. Have a look at the corresponding test
`GimpScriptRunnerTest` to see how this works.

You may import convenience functions from `pgimp.gimp` in your gimp python scripts.
You may import convenience functions from `pgimp.gimp` in your gimp python scripts.
See `pgimp.GimpScriptRunnerTest.test_import_from_pgimp_library`.

# Installation

The package manager `pip` and the python packages `setuptools` and `psutil` are required in order
to install the package. As gimp uses a python2 interpreter,
The package manager `pip` and the python packages `setuptools` and `psutil` are required in order
to install the package. As gimp uses a python2 interpreter,
the pip packages `numpy` and `typing` for python2 need to be installed.

## Operating system dependent infos

### Linux

On Linux, install the gimp package, e.g. `sudo apt-get install gimp` for Debian/Ubuntu. In order to run headless,
On Linux, install the gimp package, e.g. `sudo apt-get install gimp` for Debian/Ubuntu. In order to run headless,
install xfvb, e.g. `sudo apt-get install xvfb`.

#### Ubuntu 20.04

Newer Ubuntu versions come without python2 support for gimp. You can find a fix in the dockerfile for tests under
`test/envs/linux/ubuntu/20.04/Dockerfile`.

### Mac OS

Install gimp from gimp.org or via homebrew.
Expand Down
14 changes: 13 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version: '2'
version: '2.1'

services:
pgimp-ubuntu-16.04:
Expand All @@ -8,24 +8,36 @@ services:
image: mabu/pgimp-ubuntu-16.04:latest
volumes:
- ./:/src
shm_size: '256m'
pgimp-ubuntu-18.04:
build:
context: .
dockerfile: test/envs/linux/ubuntu/18.04/Dockerfile
image: mabu/pgimp-ubuntu-18.04:latest
volumes:
- ./:/src
shm_size: '256m'
pgimp-ubuntu-20.04:
build:
context: .
dockerfile: test/envs/linux/ubuntu/20.04/Dockerfile
image: mabu/pgimp-ubuntu-20.04:latest
volumes:
- ./:/src
shm_size: '256m'
pgimp-centos-7.4:
build:
context: .
dockerfile: test/envs/linux/centos/7.4/Dockerfile
image: mabu/pgimp-centos-7.4:latest
volumes:
- ./:/src
shm_size: '256m'
pgimp-centos-7.6:
build:
context: .
dockerfile: test/envs/linux/centos/7.6/Dockerfile
image: mabu/pgimp-centos-7.6:latest
volumes:
- ./:/src
shm_size: '256m'
44 changes: 22 additions & 22 deletions pgimp/GimpFile.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,10 +328,10 @@ def create_from_file(
>>> with TempFile('.xcf') as xcf, TempFile('.png') as png, TempFile('.xcf') as from_png:
... gimp_file = GimpFile(xcf) \\
... .create('Background', np.zeros(shape=(1, 1), dtype=np.uint8)) \\
... .add_layer_from_numpy('Foreground', np.ones(shape=(1, 1), dtype=np.uint8)*255, opacity=50.) \\
... .add_layer_from_numpy('Foreground', np.ones(shape=(1, 1), dtype=np.uint8)*255, opacity=100.) \\
... .export(png) # saved as grayscale with alpha (identify -format '%[channels]' FILE)
... GimpFile(from_png).create_from_file(png, layer_name='Image').layer_to_numpy('Image')
array([[[127, 255]]], dtype=uint8)
array([[[255, 255]]], dtype=uint8)
:param file: File to import into gimp.
:param layer_name: The layer name for the data to be imported.
Expand Down Expand Up @@ -445,7 +445,7 @@ def layers_to_numpy(
from pgimp.gimp.file import open_xcf
from pgimp.gimp.parameter import get_json, get_string
from pgimp.gimp.layer import convert_layers_to_numpy
np_buffer = convert_layers_to_numpy(open_xcf('{0:s}'), get_json('layer_names', '[]'))
temp_file = get_string('temp_file')
if temp_file:
Expand Down Expand Up @@ -564,18 +564,18 @@ def add_layers_from_numpy(
from pgimp.gimp.file import XcfFile
from pgimp.gimp.layer import add_layers_from_numpy
from pgimp.gimp.parameter import get_json, get_int, get_string
with XcfFile(get_string('file'), save=True) as image:
position = get_json('position')[0]
add_layers_from_numpy(
image, get_string('tmpfile'),
get_json('layer_names'),
get_int('width'),
get_int('height'),
get_int('layer_type'),
position,
image, get_string('tmpfile'),
get_json('layer_names'),
get_int('width'),
get_int('height'),
get_int('layer_type'),
position,
get_json('opacity')[0],
get_json('blend_mode')[0],
get_json('blend_mode')[0],
get_json('visible')[0]
)
"""
Expand Down Expand Up @@ -676,7 +676,7 @@ def add_layer_from_file(
from pgimp.gimp.parameter import get_json
from pgimp.gimp.file import XcfFile
from pgimp.gimp.layer import copy_layer
params = get_json('params')
new_position = params['new_position']
new_visibility = params['new_visibility']
Expand All @@ -698,7 +698,7 @@ def add_layer_from_file(
)

self._gsr.execute(
code,
code,
timeout_in_seconds=self.long_running_timeout_in_seconds if timeout is None else timeout,
parameters={
'params': {
Expand Down Expand Up @@ -759,7 +759,7 @@ def merge_layer_from_file(
)

self._gsr.execute(
code,
code,
timeout_in_seconds=self.long_running_timeout_in_seconds if timeout is None else timeout
)
return self
Expand All @@ -770,7 +770,7 @@ def layers(
) -> List[Layer]:
"""
Returns the image layers. The topmost layer is the first element, the bottommost the last element.
:param timeout: Execution timeout in seconds.
:return: List of :py:class:`~pgimp.layers.Layer`.
"""
Expand All @@ -794,7 +794,7 @@ def layers(
)

result = self._gsr.execute_and_parse_json(
code,
code,
timeout_in_seconds=self.short_running_timeout_in_seconds if timeout is None else timeout
)
layers = []
Expand Down Expand Up @@ -823,7 +823,7 @@ def layer_names(
... .add_layer_from_numpy('Black', np.zeros(shape=(2, 2), dtype=np.uint8))
... gimp_file.layer_names()
['Black', 'Background']
:param timeout: Execution timeout in seconds.
:return: List of layer names.
"""
Expand Down Expand Up @@ -866,7 +866,7 @@ def remove_layer(
).format(escape_single_quotes(self._file), escape_single_quotes(layer_name))

self._gsr.execute(
code,
code,
timeout_in_seconds=self.short_running_timeout_in_seconds if timeout is None else timeout
)
return self
Expand Down Expand Up @@ -901,7 +901,7 @@ def dimensions(
).format(escape_single_quotes(self._file))

dimensions = self._gsr.execute_and_parse_json(
code,
code,
timeout_in_seconds=self.short_running_timeout_in_seconds if timeout is None else timeout
)
return tuple(dimensions)
Expand All @@ -925,10 +925,10 @@ def export(
>>> with TempFile('.xcf') as xcf, TempFile('.png') as png, TempFile('.xcf') as from_png:
... gimp_file = GimpFile(xcf) \\
... .create('Background', np.zeros(shape=(1, 1), dtype=np.uint8)) \\
... .add_layer_from_numpy('Foreground', np.ones(shape=(1, 1), dtype=np.uint8)*255, opacity=50.) \\
... .add_layer_from_numpy('Foreground', np.ones(shape=(1, 1), dtype=np.uint8)*255, opacity=100.) \\
... .export(png) # saved as grayscale with alpha (identify -format '%[channels]' FILE)
... GimpFile(from_png).create_from_file(png, layer_name='Image').layer_to_numpy('Image')
array([[[127, 255]]], dtype=uint8)
array([[[255, 255]]], dtype=uint8)
:param file: Filename including the desired extension to export to.
:param timeout: Execution timeout in seconds.
Expand All @@ -947,7 +947,7 @@ def export(
).format(escape_single_quotes(self._file), escape_single_quotes(file))

self._gsr.execute(
code,
code,
timeout_in_seconds=self.short_running_timeout_in_seconds if timeout is None else timeout
)
return self
11 changes: 8 additions & 3 deletions pgimp/GimpFileTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

rgb_file = GimpFile(file.relative_to(__file__, 'test-resources/rgb.xcf'))
"""
The file rgb.xcf contains a 3x2 image with white 'Background' layer and 'Red', 'Green', 'Blue' layers with differing
The file rgb.xcf contains a 3x2 image with white 'Background' layer and 'Red', 'Green', 'Blue' layers with differing
opacity. The layer 'Background' contains a black pixel at y=0, x=1, the others pixels are white.
"""

Expand Down Expand Up @@ -330,9 +330,14 @@ def test_export():
gimp_file = GimpFile(xcf) \
.create('Background', np.zeros(shape=(1, 1), dtype=np.uint8)) \
.add_layer_from_numpy('Foreground', np.ones(shape=(1, 1), dtype=np.uint8) * 255, opacity=50.) \
# 50% opacity results in uint8 of 127 or 128 depending on the implementation -> needs test tolerance

gimp_file.export(png) # saved as grayscale with alpha (identify -format '%[channels]' FILE)
gimp_file.export(jpg)

assert np.all([127, 255] == GimpFile(from_png).create_from_file(png, layer_name='Image').layer_to_numpy('Image'))
assert np.all([127] == GimpFile(from_png).create_from_file(jpg, layer_name='Image').layer_to_numpy('Image'))
reimported_np_from_png = GimpFile(from_png).create_from_file(png, layer_name='Image').layer_to_numpy('Image')
assert np.isclose(reimported_np_from_png[0, 0, 0], 127.5, atol=0.5)
assert reimported_np_from_png[0, 0, 1] == 255

reimported_np_from_jpg = GimpFile(from_png).create_from_file(jpg, layer_name='Image').layer_to_numpy('Image')
assert np.isclose([127.5], reimported_np_from_jpg, atol=0.5)
29 changes: 29 additions & 0 deletions test/envs/linux/ubuntu/20.04/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
FROM ubuntu:20.04
MAINTAINER mathias.burger@gmail.com

ARG DEBIAN_FRONTEND=noninteractive
ENV TZ=Europe/Berlin

RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y python3 python3-pip
RUN apt-get install -y xvfb gimp python2 python-numpy python-typing

RUN cd /tmp && \
curl http://archive.ubuntu.com/ubuntu/pool/universe/g/gimp/gimp-python_2.10.8-2_amd64.deb --output gimp-python.deb && \
curl http://archive.ubuntu.com/ubuntu/pool/universe/p/pygtk/python-gtk2_2.24.0-6_amd64.deb --output python-gtk2.deb && \
apt install python python-cairo python-gobject-2 && \
dpkg -i python-gtk2.deb && \
dpkg -i gimp-python.deb && \
rm gimp-python.deb python-gtk2.deb

RUN pip3 install pytest setuptools
RUN pip2 install numpy typing

ADD requirements.txt /tmp/requirements.txt
RUN pip3 install -r /tmp/requirements.txt

VOLUME /src
WORKDIR /src

CMD ["python3"]

0 comments on commit d2eb0d1

Please sign in to comment.