Repository: PyAbel/PyAbel (language: python)

Issue:
README diagram suggestions
We recently changed the diagram in the README from this: 
![image](https://user-images.githubusercontent.com/1107796/161462379-38bd3470-97f6-4370-9c1f-5bcd11fec53f.png)

To this:
<img width="1066" alt="image" src="https://user-images.githubusercontent.com/1107796/161462457-5ac05d73-c3cf-4eff-8482-fa19dbb16af8.png">

I love that we are switching to a tiny svg file and that this allows the documentation to be built offline (thanks @MikhailRyazanov!!). But, I prefer the appearance of the first figure. I have the following suggestions to make the svg look better.

1. Change from serif to sans-serif font. Figures typically look better with sans-serif. 
2. Increase the width of the z, r, and y axes lines. Aesthetically I think that this looks better.
3. Make the z-axis on the 2D projection lighter green. I like the very bright green of the original. 
4. Even though I see that it makes sense to use 2D objects to represent the y and z axes of the 2D projection, I think that it looks prettier with the 3D axes. 
5. If 2D axes are kept for the 2D projection, I think that the z axis should still be in front of the projection data. Again, mainly for aesthetics. 
6. Does the projection in the SVG figure look qualitatively different than the original figure? To me, is seems that the "inside edge" looks a bit sharper. 
7. The angle of the original does a worse job of showing the inside of the 3D object, but it produces an image that has less wasted space above the 3D object and below the 2D projection. 
8. I think that the z axis should be aligned with one of the gridlines instead of between two of them. 
9. I prefer the way the grid looks in the original: finer grid, lighter gray gridlines, and lighter gray background. 

Okay, that's what I got. Sorry to nitpick. Accept or reject those as you see fit. 

(MR edit: I've converted the list to numbered, for referencing.)


Discussion / hints:
Oh, one more thing, on my screen the figure looks a bit small, and should probably be made larger in the readme. Also, should it be centered?

<img width="882" alt="image" src="https://user-images.githubusercontent.com/1107796/161463600-19fd77cc-7503-479c-8bfe-e301031cc86b.png">

OK, I was surprised that my *draft* figure was accepted without any discussion, :–) so let's finally try to improve it...

Point by point:

1. Already done in #349 (8b5d93a). This was my coding error in SVG font specifications (the PDF font has been always correct because it is my default system font). Can you please confirm that the new SVG renders correctly in macOS browsers?
2. See 8 below.
3. I was trying to maintain reasonable contrast, and even now the *z* axis is brighter than the other two:
![overview_bw](https://user-images.githubusercontent.com/12854133/161608815-63450d4c-ff54-42bb-ab6c-8949b4e1f829.png)
But I can make it lighter to see whether it would look better.
4. Yes, the intention was to show 2D in 2D. Implementing proper 3D clipping might need some extra effort but is doable, I can try.
5. The idea was to demonstrate that the object is really projected “on” the detector plane. However, drawing the coordinates over it would be fine too.
6. Since drawing a real paraboloid / shaded parabola in SVG is not trivial, I in fact used a truncated very elongated ellipsoid / ellipse (in the limit, this would give the original shapes). The Abel projection in this case is analytic and depends only on the “stretched radius” (rigorously, it is an appropriately stretched projection of a spherical shell), so it is easily drawn by filling the ellipse with a “radial gradient”. It indeed has both sharp edges, see the [plot of StepAnalytical](https://pyabel.readthedocs.io/en/latest/abel.html#abel.tools.analytical.StepAnalytical). I guess, your figure used a relatively low-resolution numerical image, antialiased by the renderer, so it looked smoother than it should.
7. I was trying to show more volume/depth, but it can be decreased, of course. The problem here is that your render uses noticeable perspective, but implementing it properly in SVG would require much more effort than the simulated parallel projection. Notice that the *y* axes in both images point in very similar directions, but the upper part of your render is much more horizontal. If I bring the “overall angle” closer to yours, unfortunately the whole image will look much flatter. Though, maybe it would be fine, I can try.
8. The idea here was to show that we use pixel-centered coordinates. This also determines that the axes are thinner and the grig (which is supposed to demonstrate the pixel grid) is coarser. If this doesn't sound important, I can easily make the grid finer and the axes thicker and aligned with the grid.
9. See 8 above. The colors can be adjusted, of course.

Tell me what you think, and I'll prepare an updated version!

About centering and size:

GitHub ignores image alignment (see https://github.com/github/markup/issues/163). I can add `:align: center` back (and it is currently used in the built documentation), but this is unlikely to help (although PyPI might respect it). We can also try to use raw HTML, as suggested in the linked discussion, but I consider this less desirable and more fragile. By the way, should we also center the example output or not?

I think, `:width:` is also ignored by GitHub, but I can just make the image larger and then scale it down in other places (if it doesn't fit automatically).

Thanks for making the changes!

For some reason, the figure you included is in grayscale.

1. Yes, the font looks good now!
2. Ah, I see what you mean about the pixel-center grid. I don't think that this figure needs to communicate everything about our implementation of Abel transforms. I'd pick a prettier figure over showing that our implementation is center-pixel-at-center.
3. Yeah, I think that lighter would be nice.
4. Okay, if you want to leave the 2D axes 2D, that is okay. 
5. I think that it just looks nicer with the z-axis in front of  the image, but I see what you mean.
6. Okay, what you are saying about the projection makes sense to me.
7. Ok.

Okay, if it's tough to center on github then we don't need to worry about it. Minor issue, and if it looks centered in the docs, then this is fine. We should prioritize the readme to look good in the docs, and as long as it doesn't look completely terrible on GitHub, then this is fine.
I haven't made any changes yet, waiting for your reply first! :–) The figure in grayscale is the same SVG figure, simply converted to grayscale PNG to illustrate the lightness issues. I don't think that we have any users with total color blindness, so the green axis of any lightness will be distinguishable from the gray background, but I just generally prefer similar items to be perceptually similar (compare the “Forward” and “Inverse” arrows, for example)... However, I'll try to make the *z* axis lighter and place the axes over the image, so they will be more visible.
Here is a new version, with most issues addressed and some other changes:
![overview](https://user-images.githubusercontent.com/12854133/161799385-dd49b2d7-4a93-4699-96dc-b51399fea2e6.svg)
I've also tried to put the grid over the projection, but it looked very bad (making grid lines thinner would probably help, but then there will be problems rendering them on lower-resolution screens).

Does it look better? Any further suggestions?

By the way, the original figure in GitHub README [was](https://github.com/PyAbel/PyAbel/tree/comparison#user-content-introduction) even smaller. It was supposed to be right-floated (see how it appeared [on PyPI](https://pypi.org/project/PyAbel/0.8.5/) and [on RTD](https://pyabel.readthedocs.io/en/v0.8.5/readme_link.html#introduction)), but GitHub didn't understand that either, and on RTD and PyPI the text wrapped around it was too narrow. I think, current size and placement are at least not worse than they were before. And the font size more or less matches the text. :–)

Wow! This new version looks awesome!

Should the z, y, and r labels be boldface to make them a little easier to see? Otherwise I think this is great. 


> Should the z, y, and r labels be boldface to make them a little easier to see?

Let's try:
![overview_bold_ax](https://user-images.githubusercontent.com/12854133/161860744-1aadd7a3-da7d-424e-bb4f-ff7bf3921633.svg)

And, just in case, with boldface “transforms” as well (hopefully, the text fits correctly in all browsers/systems — I don't want to make the arrows even longer):
![overview_bold](https://user-images.githubusercontent.com/12854133/161860806-6a99fbe9-d514-44a8-99dc-6be7397a5907.svg)

Looks good to me! I like the bold axes labels. I think that I prefer the bold labels on the abel transform arrows too.

Thanks for making the changes. looks great!

> bold labels on the abel transform arrows too

OK, let's then use the latest version. After all, PyAbel is about **Abel transforms**. :–)

I've checked that it is also rendered correctly even by IE11 in Win7 (GitHub interface there is broken, but the images themselves look good):
![netrenderer](https://user-images.githubusercontent.com/12854133/162044646-aa975a31-2c96-4e07-81e5-ba74dc121de7.png)
So it should be safe to use this SVG everywhere.

Please take a look at #351. If we can decide on that, I'll just include this new diagram in that PR instead of making a separate PR for simple cosmetic improvements.

Fix patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 3373a833..eb57ffa8 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -4,12 +4,14 @@ Changelog
 Unreleased
 ----------
 
+* Offline HTML and PDF documentation is now available at Read the Docs and can
+  be built locally (PR #343, #348, #349).
 * Correct behavior of relative basis_dir in basex under Python 2 (PR #336).
 * Analytical Abel transform of axially symmetric piecewise polynomials in
   spherical coordinates (PR #339).
 * Improvements in tools.analytical.SampleImage class: more consistent and
   intuitive interface, accurate Abel transform for existing images, additional
-  sample images with exact Abel transform (PR #339).
+  sample images with exact Abel transform (PR #339, #352).
 
 v0.8.5 (2022-01-21)
 -------------------
diff --git a/README.rst b/README.rst
index c0700cea..1f2ec1ec 100644
--- a/README.rst
+++ b/README.rst
@@ -24,6 +24,7 @@ Introduction
 .. begin-github-only2
 
 .. image:: https://raw.githubusercontent.com/PyAbel/PyAbel/master/doc/overview.svg
+    :align: center
 
 .. end-github-only2
 
@@ -96,12 +97,12 @@ and *then* proceed with the usual module uninstallation process (for example, ``
 Example of use
 --------------
 
-Using PyAbel can be simple. The following Python code imports the PyAbel package, generates a sample image, performs a forward transform using the Hansen–Law method, and then a reverse transform using the Three Point method:
+Using PyAbel can be simple. The following Python code imports the PyAbel package, generates a sample image, performs a forward transform using the Hansen–Law method, and then an inverse transform using the Three Point method:
 
 .. code-block:: python
 
     import abel
-    original     = abel.tools.analytical.SampleImage().func
+    original = abel.tools.analytical.SampleImage(name='Gerber').func
     forward_abel = abel.Transform(original, direction='forward',
                                   method='hansenlaw').transform
     inverse_abel = abel.Transform(forward_abel, direction='inverse',
@@ -118,8 +119,8 @@ The results can then be plotted using Matplotlib:
 
     fig, axs = plt.subplots(1, 2, figsize=(6, 3))
 
-    axs[0].imshow(forward_abel, cmap='ocean_r')
-    axs[1].imshow(inverse_abel, cmap='ocean_r')
+    axs[0].imshow(forward_abel, clim=(0, None), cmap='ocean_r')
+    axs[1].imshow(inverse_abel, clim=(0, None), cmap='ocean_r')
 
     axs[0].set_title('Forward Abel transform')
     axs[1].set_title('Inverse Abel transform')
diff --git a/abel/tools/analytical.py b/abel/tools/analytical.py
index b76a0e7b..216eceea 100644
--- a/abel/tools/analytical.py
+++ b/abel/tools/analytical.py
@@ -419,6 +419,13 @@ class SampleImage(BaseAnalytical):
 
             Its Abel transform is also a Gaussian with the same width:
             :math:`\sqrt{\pi}\, \textbf{sigma} \exp(-r^2 / \textbf{sigma}^2)`.
+        ``'Gerber'``
+            Artificial test image used in the lin-BASEX article
+            `Rev. Sci. Instrum. 84, 033101 (2013)
+            <https://dx.doi.org/10.1063/1.4793404>`__, Table I.
+
+            8 Gaussian peaks with various intensities and anisotropies up to
+            4th order (β\ :sub:`4`).
         ``'O2'``
             Synthetic image mimicking a velocity-map image of O\ :sup:`+` from
             multiphoton photodissociation/ionization
@@ -437,7 +444,8 @@ class SampleImage(BaseAnalytical):
         1/*e* halfwidth of peaks in pixels, default values are:
         2⋅\ *r*\ :sub:`max`/180 for ``'Dribinski'``,
         2⋅\ *r*\ :sub:`max`/500 for ``'Ominus'``,
-        *r*\ :sub:`max`/3 for ``'Gaussian'``.
+        *r*\ :sub:`max`/3 for ``'Gaussian'``,
+        :math:`\sqrt{2}` (std. dev. = 1) for ``'Gerber'``.
 
         For ``'O2'``: HWHM of narrow peaks in pixels, default is 1.5 for any
         *r*\ :sub:`max`.
@@ -478,8 +486,28 @@ def __init__(self, n=361, name='Dribinski', sigma=None, temperature=200):
             self.name = 'Gaussian'
             self._scale = 0
             width = self.r_max / 3 if sigma is None else sigma
-            # parameters:  A r0  width  angular
+            # parameters:   A  r0 width  angular
             self._peaks = [(1, 0, width, [1])]
+        elif name == 'gerber':
+            self.name = 'Gerber'
+            self._scale = self.r_max / 256
+            width = np.sqrt(2) if sigma is None else sigma
+
+            def sphere(r, beta0, beta2, beta4):
+                N = 160000 / (4 * np.pi * np.sqrt(np.pi) * width)
+                return (N / r**2, r, width,
+                        beta0 * np.array([1, 0, 0, 0, 0]) +
+                        beta2 * np.array([-1/2, 0, 3/2, 0, 0]) +
+                        beta4 * np.array([3/8, 0, -30/8, 0, 35/8]))
+            # parameters:         r0   beta0 beta2 beta4
+            self._peaks = [sphere(38,  1.2, -0.4,  0),
+                           sphere(70,  1.5, -1,    0.5),
+                           sphere(90,  1.5,  1,    0.4),
+                           sphere(134, 2,    1,    0.4),
+                           sphere(138, 1.8,  0.5,  0),
+                           sphere(143, 1,    0.5,  0.25),
+                           sphere(196, 2,    1,   -0.5),
+                           sphere(230, 2,    1,   -0.5)]
         elif name == 'o2':
             self.name = 'O2'
             self._scale = self.r_max / 330
diff --git a/abel/tools/vmi.py b/abel/tools/vmi.py
index 1c15e00f..f4d2cbb4 100644
--- a/abel/tools/vmi.py
+++ b/abel/tools/vmi.py
@@ -250,7 +250,7 @@ def radial_integration(IM, origin=None, radial_ranges=None):
     r""" Intensity variation in the angular coordinate.
 
     This function is the :math:`\theta`-coordinate complement to
-    :func:`abel.tools.vmi.angular_integration`.
+    :func:`abel.tools.vmi.average_radial_intensity_3D`.
 
     Evaluates intensity vs angle for defined radial ranges.
     Determines the anisotropy parameter for each radial range.
@@ -266,14 +266,16 @@ def radial_integration(IM, origin=None, radial_ranges=None):
         image origin in the (row, column) format. If ``None``, the geometric
         center of the image (``rows // 2, cols // 2``) is used.
 
-    radial_ranges : list of tuple ranges or int step
-        tuple
+    radial_ranges : list of tuple or int
+        list of tuple
             integration ranges
-            ``[(r0, r1), (r2, r3), ...]``
-            evaluates the intensity vs angle
+            ``[(r0, r1), (r2, r3), ...]``.
+            Evaluates the intensity vs angle
             for the radial ranges ``r0_r1``, ``r2_r3``, etc.
 
         int
+            radial step.
+            Evaluates the intensity vs angle for
             the whole radial range ``(0, step), (step, 2*step), ..``
 
     Returns
diff --git a/doc/overview.pdf b/doc/overview.pdf
index 95830f13..61425bc9 100644
Binary files a/doc/overview.pdf and b/doc/overview.pdf differ
diff --git a/doc/overview.svg b/doc/overview.svg
index 77566559..6ad063bd 100644
--- a/doc/overview.svg
+++ b/doc/overview.svg
@@ -4,39 +4,39 @@ Drawn by Mikhail Ryazanov based on PNG render by Daniel Hickstein
 (see doi:10.1063/1.5092635 and Rev. Sci. Instrum. 90(6) cover).
 
 Validate:
-  https://validator.w3.org/check
+  https://validator.w3.org/#validate_by_upload
 
 Convert to PDF:
   inkscape -o overview.pdf overview.svg
 
 -->
 <svg xmlns='http://www.w3.org/2000/svg' version='1.1'
-     width='6in' height='3.5in' viewBox='0 0 600 350'>
+     width='6in' height='3in' viewBox='0 0 600 300'>
   <defs>
     <!-- 3D origin -->
     <radialGradient id='origin' cx='0.4' cy='0.3'>
-      <stop offset='0' stop-color='#666'/>
-      <stop offset='1' stop-color='#000'/>
+      <stop offset='0' stop-color='#CCC'/>
+      <stop offset='1' stop-color='#666'/>
     </radialGradient>
 
     <!-- 3D r axis -->
     <linearGradient id='r_axis'>
-      <stop offset='0.0' stop-color='#D0D'/>
-      <stop offset='0.2' stop-color='#E0E'/>
-      <stop offset='0.4' stop-color='#D0D'/>
-      <stop offset='0.6' stop-color='#C0C'/>
-      <stop offset='0.8' stop-color='#909'/>
-      <stop offset='1.0' stop-color='#707'/>
+      <stop offset='0.0' stop-color='#E0E'/>
+      <stop offset='0.2' stop-color='#F0F'/>
+      <stop offset='0.4' stop-color='#E0E'/>
+      <stop offset='0.6' stop-color='#D0D'/>
+      <stop offset='0.8' stop-color='#A0A'/>
+      <stop offset='1.0' stop-color='#808'/>
     </linearGradient>
     <!-- its head (fake conical gradient) -->
     <radialGradient id='r_head' r='112.4' cx='-100' cy='50' fx='0.28' fy='-0.5'>
-      <stop offset='0.000' stop-color='#C0C'/>
-      <stop offset='0.013' stop-color='#D0D'/>
-      <stop offset='0.070' stop-color='#707'/>
+      <stop offset='0.000' stop-color='#D0D'/>
+      <stop offset='0.013' stop-color='#E0E'/>
+      <stop offset='0.070' stop-color='#808'/>
     </radialGradient>
 
     <!-- 3D z axis -->
-    <linearGradient id='z_axis'>
+    <linearGradient id='z_axis_bottom'>
       <stop offset='0.0' stop-color='#090'/>
       <stop offset='0.2' stop-color='#0B0'/>
       <stop offset='0.4' stop-color='#0C0'/>
@@ -44,11 +44,19 @@ Convert to PDF:
       <stop offset='0.8' stop-color='#090'/>
       <stop offset='1.0' stop-color='#070'/>
     </linearGradient>
+    <linearGradient id='z_axis_top'>
+      <stop offset='0.0' stop-color='#0C0'/>
+      <stop offset='0.2' stop-color='#0D0'/>
+      <stop offset='0.4' stop-color='#0E0'/>
+      <stop offset='0.6' stop-color='#0D0'/>
+      <stop offset='0.8' stop-color='#0C0'/>
+      <stop offset='1.0' stop-color='#0A0'/>
+    </linearGradient>
     <!-- its head (fake conical gradient) -->
     <radialGradient id='z_head' r='112.4' cx='-100' cy='50' fx='0.28' fy='-0.5'>
-      <stop offset='0.000' stop-color='#0A0'/>
-      <stop offset='0.013' stop-color='#0D0'/>
-      <stop offset='0.070' stop-color='#0B0'/>
+      <stop offset='0.000' stop-color='#0C0'/>
+      <stop offset='0.013' stop-color='#0F0'/>
+      <stop offset='0.070' stop-color='#0D0'/>
     </radialGradient>
 
     <!-- object outer shading -->
@@ -92,21 +100,19 @@ Convert to PDF:
     </radialGradient>
   </defs>
 
-  <g font-family='DejaVu Sans, Verdana, sans-serif' font-size='16'>
+  <g font-family='DejaVu Sans, Verdana, sans-serif' font-size='16' font-weight='bold'>
     <!-- Object -->
-    <g transform='translate(90 290)'>
+    <g transform='translate(90 255)'>
       <!-- origin -->
-      <circle r='5' fill='url(#origin)'/>
+      <circle r='6' fill='url(#origin)'/>
       <!-- r axis -->
-      <g transform='rotate(105)'>
-        <path d='M -3 -3.5 a 3 1.5 0 0 0 6 0 l 0 -90 l -6 0 z' fill='url(#r_axis)'/>
-        <path d='M -8 -90 a 8 3 0 1 0 16 0 l -8 -14 l -8 14 z' fill='url(#r_head)'/>
+      <g transform='rotate(100)'>
+        <path d='M -4 -4 a 4 1.5 0 0 0 8 0 l 0 -90 l -8 0 z' fill='url(#r_axis)'/>
+        <path d='M -9 -90 a 9 3 0 1 0 18 0 l -9 -17 l -9 17 z' fill='url(#r_head)'/>
       </g>
-      <text x='88' y='44' text-anchor='middle' font-style='italic' fill='#C0C'>r</text>
-      <!-- z axis -->
-      <path d='M -3 -3.5 a 3 1 0 0 0 6 0 l 0 -220 l -6 0 z' fill='url(#z_axis)'/>
-      <path d='M -8 -220 a 8 2 0 1 0 16 0 l -8 -16 l -8 16 z' fill='url(#z_head)'/>
-      <text x='-15' y='-220' text-anchor='middle' font-style='italic' fill='#0A0'>z</text>
+      <text x='95' y='35' text-anchor='middle' font-style='italic' fill='#D0D'>r</text>
+      <!-- z axis (bottom) -->
+      <path d='M -4 -4 a 4 1 0 0 0 8 0 l 0 -220 l -8 0 z' fill='url(#z_axis_bottom)'/>
       <!-- object... -->
       <!-- outer -->
       <clipPath id='object_clip_out'>
@@ -118,125 +124,127 @@ Convert to PDF:
       <!-- inner -->
       <clipPath id='object_clip_in'>
         <!-- (1px larger to avoid edge-stitching artifacts) -->
-        <path d='M -68 -180 a 68 18 0 0 0 136 0 a 68 18 0 0 0 -136 0 z'/>
+        <path d='M -68 -180 a 68 13 0 0 0 136 0 a 68 13 0 0 0 -136 0 z'/>
       </clipPath>
       <g clip-path='url(#object_clip_in)'>
         <circle r='360' transform='translate(0 -420) scale(0.25 1)' fill='url(#object_in)'/>
       </g>
       <!-- top -->
-      <path d='M -80 -180 a 80 20 0 0 0 160 0 a 80 20 0 0 0 -160 0 z
-               M -67 -180 a 67 17 0 0 0 134 0 a 67 17 0 0 0 -134 0 z'
+      <path d='M -80 -180 a 80 14 0 0 0 160 0 a 80 14 0 0 0 -160 0 z
+               M -67 -180 a 67 12 0 0 0 134 0 a 67 12 0 0 0 -134 0 z'
                fill-rule='evenodd' fill='#4AF'/>
-      <!-- inner z axis -->
+      <!-- z axis (top) -->
       <clipPath id='object_clip_axis'>
-        <path d='M -67 -180 a 67 17 0 0 0 134 0 l 0 -20 l -107 0 z'/>
+        <path d='M -67 -180 a 67 12 0 0 0 134 0 l 0 -50 l -107 0 z'/>
       </clipPath>
       <path clip-path='url(#object_clip_axis)'
-            d='M -3 -3 a 3 1 0 0 0 6 0 l 0 -220 l -6 0 z' fill='url(#z_axis)'/>
+            d='M -4 -4 a 4 1 0 0 0 8 0 l 0 -220 l -8 0 z' fill='url(#z_axis_top)'/>
+      <path d='M -9 -220 a 9 1.5 0 1 0 18 0 l -9 -18 l -9 18 z' fill='url(#z_head)'/>
+      <text x='-15' y='-225' text-anchor='middle' font-style='italic' fill='#0E0'>z</text>
       <!-- label -->
       <g fill='#FFF' text-anchor='middle'>
-        <text y='-130' font-weight='bold'>3D object</text>
-        <g font-size='80%'>
-          <text y='-110'>(cylindrically</text>
-          <text y='-95'>symmetric)</text>
+        <!--text y='-135' font-weight='bold'>3D object</text-->
+        <g transform='translate(0 -135)' font-weight='bold'>
+          <text transform='translate(-37.5 -2  ) skewY( 6  )'>3</text>
+          <text transform='translate(-25.2 -1  ) skewY( 3.5)'>D</text>
+          <text transform='translate( -7.5 -0.2) skewY( 1  )'>o</text>
+          <text transform='translate(  3.7 -0.2) skewY(-1  )'>b</text>
+          <text transform='translate( 12.2 -0.4) skewY(-1.5)'>j</text>
+          <text transform='translate( 20.5 -0.7) skewY(-3.0)'>e</text>
+          <text transform='translate( 30.5 -1.4) skewY(-4.5)'>c</text>
+          <text transform='translate( 39   -2.2) skewY(-6.5)'>t</text>
+        </g>
+        <g font-size='80%' font-weight='normal'>
+          <!--text y='-115'>(cylindrically</text-->
+          <g transform='translate(0 -115)'>
+            <text transform='translate(-39.5 -2.3) skewY( 7  )'>(</text>
+            <text transform='translate(-33.4 -1.6) skewY( 6  )'>c</text>
+            <text transform='translate(-26.1 -0.9) skewY( 4.5)'>y</text>
+            <text transform='translate(-20.2 -0.5) skewY( 3  )'>l</text>
+            <text transform='translate(-16.3 -0.4) skewY( 2.5)'>i</text>
+            <text transform='translate(-10.5 -0.2) skewY( 1.5)'>n</text>
+            <text transform='translate( -2.3  0  ) skewY( 0.5)'>d</text>
+            <text transform='translate(  4.5  0  ) skewY(-0.5)'>r</text>
+            <text transform='translate(  9.2 -0.1) skewY(-1.5)'>i</text>
+            <text transform='translate( 14.5 -0.3) skewY(-2.5)'>c</text>
+            <text transform='translate( 21.9 -0.7) skewY(-3.5)'>a</text>
+            <text transform='translate( 28   -1.1) skewY(-4.5)'>l</text>
+            <text transform='translate( 32.2 -1.5) skewY(-5.5)'>l</text>
+            <text transform='translate( 38.1 -2.1) skewY(-7  )'>y</text>
+          </g>
+          <!--text y='-100'>symmetric)</text-->
+          <g transform='translate(0 -100)'>
+            <text transform='translate(-33.3 -1.7) skewY( 6.5)'>s</text>
+            <text transform='translate(-26.2 -1.0) skewY( 4.5)'>y</text>
+            <text transform='translate(-16.2 -0.4) skewY( 2.5)'>m</text>
+            <text transform='translate( -3.7 -0.1) skewY( 0.5)'>m</text>
+            <text transform='translate(  6.5 -0.1) skewY(-1  )'>e</text>
+            <text transform='translate( 12.9 -0.3) skewY(-2  )'>t</text>
+            <text transform='translate( 18.2 -0.5) skewY(-3  )'>r</text>
+            <text transform='translate( 22.9 -0.8) skewY(-4  )'>i</text>
+            <text transform='translate( 28.1 -1.2) skewY(-5  )'>c</text>
+            <text transform='translate( 34.1 -1.8) skewY(-6.5)'>)</text>
+          </g>
         </g>
       </g>
     </g>
 
     <!-- Inverse arrow -->
-    <g transform='translate(290 100) skewY(-15) scale(0.866 1)'>
+    <g transform='translate(290 100) skewY(-10) scale(0.866 1)'>
       <!-- arrow -->
       <path d='M 110 -20 l 0 40 l -180 0 l 0 20 l -50 -40 l 50 -40 l 0 20 z' fill='#D0D'/>
       <!-- top -->
-      <path d='M 110 -20 l -10 -5 l -170 0 l 0 5 z' fill='#E8E'/>
+      <path d='M 110 -20 l -16 -5 l -164 0 l 0 5 z' fill='#E8E'/>
       <!-- head top -->
-      <path d='M -70 -40 l -10 -5 l -50 40 l 10 5 z' fill='#E8E'/>
+      <path d='M -70 -40 l -16 -5 l -50 40 l 16 5 z' fill='#E8E'/>
       <!-- head bottom -->
-      <path d='M -120 0 l -10 -5 l 50 40 l 10 5 z' fill='#B0B'/>
+      <path d='M -120 0 l -16 -5 l 50 40 l 16 5 z' fill='#B0B'/>
       <!-- label -->
       <text x='0' y='4' text-anchor='middle' fill='#FFF'>Inverse Abel transform</text>
     </g>
 
     <!-- Forward arrow -->
-    <g transform='translate(290 200) skewY(-15) scale(0.866 1)'>
+    <g transform='translate(290 180) skewY(-10) scale(0.866 1)'>
       <!-- arrow -->
       <path d='M -110 -20 l 0 40 l 180 0 l 0 20 l 50 -40 l -50 -40 l 0 20 z' fill='#D44'/>
       <!-- left -->
-      <path d='M -110 20 l -10 -5 l 0 -40 l 10 5 z' fill='#C44'/>
+      <path d='M -110 20 l -16 -5 l 0 -40 l 16 5 z' fill='#C44'/>
       <!-- top -->
-      <path d='M -110 -20 l -10 -5 l 180 0 l 10 5 z' fill='#E88'/>
+      <path d='M -110 -20 l -16 -5 l 180 0 l 16 5 z' fill='#E88'/>
       <!-- head left top -->
-      <path d='M 70 -20 l -10 -5 l 0 -20 l 10 5 z' fill='#C44'/>
+      <path d='M 70 -20 l -16 -5 l 0 -20 l 16 5 z' fill='#C44'/>
       <!-- head left bottom -->
-      <path d='M 70 40 l -10 -5 l 0 -15 l 10 0 z' fill='#C44'/>
+      <path d='M 70 40 l -16 -5 l 0 -15 l 16 0 z' fill='#C44'/>
       <!-- label -->
       <text x='0' y='4' text-anchor='middle' fill='#FFF'>Forward Abel transform</text>
     </g>
 
     <!-- Projection -->
-    <g transform='translate(400 20) skewY(15) scale(0.866 1)'>
+    <g transform='translate(400 20) skewY(10) scale(0.866 1)'>
       <!-- plane -->
-      <rect width='190' height='200' fill='#EEE'/>
+      <rect width='190' height='200' fill='#F7F7F7'/>
       <!-- grid -->
-      <g stroke='#888' stroke-linecap='round'>
+      <g stroke='#DDD' stroke-linecap='round'>
         <!-- horizontal -->
-        <line x1='0' y1=  '0' x2='190' y2=  '0'/>
-        <line x1='0' y1= '10' x2='190' y2= '10'/>
-        <line x1='0' y1= '20' x2='190' y2= '20'/>
-        <line x1='0' y1= '30' x2='190' y2= '30'/>
-        <line x1='0' y1= '40' x2='190' y2= '40'/>
-        <line x1='0' y1= '50' x2='190' y2= '50'/>
-        <line x1='0' y1= '60' x2='190' y2= '60'/>
-        <line x1='0' y1= '70' x2='190' y2= '70'/>
-        <line x1='0' y1= '80' x2='190' y2= '80'/>
-        <line x1='0' y1= '90' x2='190' y2= '90'/>
-        <line x1='0' y1='100' x2='190' y2='100'/>
-        <line x1='0' y1='110' x2='190' y2='110'/>
-        <line x1='0' y1='120' x2='190' y2='120'/>
-        <line x1='0' y1='130' x2='190' y2='130'/>
-        <line x1='0' y1='140' x2='190' y2='140'/>
-        <line x1='0' y1='150' x2='190' y2='150'/>
-        <line x1='0' y1='160' x2='190' y2='160'/>
-        <line x1='0' y1='170' x2='190' y2='170'/>
-        <line x1='0' y1='180' x2='190' y2='180'/>
-        <line x1='0' y1='190' x2='190' y2='190'/>
-        <line x1='0' y1='200' x2='190' y2='200'/>
+        <path d='M 0 0 H 190 m 0 5 H 0 m 0 5 H 190 m 0 5 H 0 m 0 5 H 190
+                 m 0 5 H 0 m 0 5 H 190 m 0 5 H 0 m 0 5 H 190 m 0 5 H 0
+                 m 0 5 H 190 m 0 5 H 0 m 0 5 H 190 m 0 5 H 0 m 0 5 H 190
+                 m 0 5 H 0 m 0 5 H 190 m 0 5 H 0 m 0 5 H 190 m 0 5 H 0
+                 m 0 5 H 190 m 0 5 H 0 m 0 5 H 190 m 0 5 H 0 m 0 5 H 190
+                 m 0 5 H 0 m 0 5 H 190 m 0 5 H 0 m 0 5 H 190 m 0 5 H 0
+                 m 0 5 H 190 m 0 5 H 0 m 0 5 H 190 m 0 5 H 0 m 0 5 H 190
+                 m 0 5 H 0 m 0 5 H 190 m 0 5 H 0 m 0 5 H 190 m 0 5 H 0
+                 m 0 5 H 190'/>
         <!-- vertical -->
-        <line x1=  '0' y1='0' x2=  '0' y2='200'/>
-        <line x1= '10' y1='0' x2= '10' y2='200'/>
-        <line x1= '20' y1='0' x2= '20' y2='200'/>
-        <line x1= '30' y1='0' x2= '30' y2='200'/>
-        <line x1= '40' y1='0' x2= '40' y2='200'/>
-        <line x1= '50' y1='0' x2= '50' y2='200'/>
-        <line x1= '60' y1='0' x2= '60' y2='200'/>
-        <line x1= '70' y1='0' x2= '70' y2='200'/>
-        <line x1= '80' y1='0' x2= '80' y2='200'/>
-        <line x1= '90' y1='0' x2= '90' y2='200'/>
-        <line x1='100' y1='0' x2='100' y2='200'/>
-        <line x1='110' y1='0' x2='110' y2='200'/>
-        <line x1='120' y1='0' x2='120' y2='200'/>
-        <line x1='130' y1='0' x2='130' y2='200'/>
-        <line x1='140' y1='0' x2='140' y2='200'/>
-        <line x1='150' y1='0' x2='150' y2='200'/>
-        <line x1='160' y1='0' x2='160' y2='200'/>
-        <line x1='170' y1='0' x2='170' y2='200'/>
-        <line x1='180' y1='0' x2='180' y2='200'/>
-        <line x1='190' y1='0' x2='190' y2='200'/>
-      </g>
-      <!-- x axis -->
-      <g fill='#C00'>
-        <path d='M 95 195 l 115 0 m 0 0 l -6 3 l 0 -6 l 6 3 z'
-              stroke='#C00' stroke-width='6' stroke-miterlimit='10'/>
-        <text x='200' y='215' text-anchor='middle' font-style='italic'>y</text>
-      </g>
-      <!-- z axis -->
-      <g fill='#0A0'>
-        <path d='M 95 195 l 0 -215 m 0 0 l 3 6 l -6 0 l 3 -6 z'
-              stroke='#0A0' stroke-width='6' stroke-miterlimit='10'/>
-        <text x='80' y='-10' text-anchor='middle' font-style='italic'>z</text>
+        <path d='M 0 0 V 200 m 5 0 V 0 m 5 0 V 200 m 5 0 V 0 m 5 0 V 200
+                 m 5 0 V 0 m 5 0 V 200 m 5 0 V 0 m 5 0 V 200 m 5 0 V 0
+                 m 5 0 V 200 m 5 0 V 0 m 5 0 V 200 m 5 0 V 0 m 5 0 V 200
+                 m 5 0 V 0 m 5 0 V 200 m 5 0 V 0 m 5 0 V 200 m 5 0 V 0
+                 m 5 0 V 200 m 5 0 V 0 m 5 0 V 200 m 5 0 V 0 m 5 0 V 200
+                 m 5 0 V 0 m 5 0 V 200 m 5 0 V 0 m 5 0 V 200 m 5 0 V 0
+                 m 5 0 V 200 m 5 0 V 0 m 5 0 V 200 m 5 0 V 0 m 5 0 V 200
+                 m 5 0 V 0 m 5 0 V 200 m 5 0 V 0 m 5 0 V 200'/>
       </g>
-      <!-- origin -->
-      <circle cx='95' cy='195' r='5' fill='#000'/>
       <!-- projection -->
       <clipPath id='projection_clip'>
         <rect y='20' width='190' height='180'/>
@@ -244,8 +252,22 @@ Convert to PDF:
       <g clip-path='url(#projection_clip)'>
         <circle r='400' transform='translate(95 -220) scale(0.25 1)' fill='url(#projection)'/>
       </g>
+      <!-- y axis -->
+      <g fill='#E00'>
+        <path d='M 95 200 l 115 0 m 0 0 l -6 3 l 0 -6 l 6 3 z'
+              stroke='#E00' stroke-width='8' stroke-miterlimit='10'/>
+        <text x='210' y='220' text-anchor='middle' font-style='italic'>y</text>
+      </g>
+      <!-- z axis -->
+      <g fill='#0E0'>
+        <path d='M 95 200 l 0 -220 m 0 0 l 3 6 l -6 0 l 3 -6 z'
+              stroke='#0E0' stroke-width='8' stroke-miterlimit='10'/>
+        <text x='80' y='-15' text-anchor='middle' font-style='italic'>z</text>
+      </g>
+      <!-- origin -->
+      <circle cx='95' cy='200' r='6' fill='#999'/>
       <!-- label -->
-      <text x='95' y='225' text-anchor='middle' font-weight='bold'>2D projection</text>
+      <text x='95' y='230' text-anchor='middle' font-weight='bold'>2D projection</text>
     </g>
   </g>
 </svg>
diff --git a/doc/readme_link.rst b/doc/readme_link.rst
index 01b0fe2d..0c92e8bf 100644
--- a/doc/readme_link.rst
+++ b/doc/readme_link.rst
@@ -18,7 +18,7 @@
 .. plot::
 
     import abel
-    original     = abel.tools.analytical.SampleImage().func
+    original = abel.tools.analytical.SampleImage(name='Gerber').func
     forward_abel = abel.Transform(original, direction='forward',
                                   method='hansenlaw').transform
     inverse_abel = abel.Transform(forward_abel, direction='inverse',
@@ -29,8 +29,8 @@
 
     fig, axs = plt.subplots(1, 2, figsize=(6, 3))
 
-    axs[0].imshow(forward_abel, cmap='ocean_r')
-    axs[1].imshow(inverse_abel, cmap='ocean_r')
+    axs[0].imshow(forward_abel, clim=(0, None), cmap='ocean_r')
+    axs[1].imshow(inverse_abel, clim=(0, None), cmap='ocean_r')
 
     axs[0].set_title('Forward Abel transform')
     axs[1].set_title('Inverse Abel transform')
diff --git a/examples/example_O2_PES_PAD.py b/examples/example_O2_PES_PAD.py
index 2cc69d68..5d855466 100644
--- a/examples/example_O2_PES_PAD.py
+++ b/examples/example_O2_PES_PAD.py
@@ -10,13 +10,13 @@
 import matplotlib.pylab as plt
 
 # This example demonstrates Hansen and Law inverse Abel transform
-# of an image obtained using a velocity map imaging (VMI) photoelecton 
-# spectrometer to record the photoelectron angular distribution resulting 
-# from photodetachement of O2- at 454 nm. 
+# of an image obtained using a velocity map imaging (VMI) photoelecton
+# spectrometer to record the photoelectron angular distribution resulting
+# from photodetachement of O2- at 454 nm.
 # Measured at  The Australian National University
 # J. Chem. Phys. 133, 174311 (2010) DOI: 10.1063/1.3493349
 
-# Load image as a numpy array - numpy handles .gz, .bz2 
+# Load image as a numpy array - numpy handles .gz, .bz2
 imagefile = bz2.BZ2File('data/O2-ANU1024.txt.bz2')
 IM = np.loadtxt(imagefile)
 # use scipy.misc.imread(filename) to load image formats (.png, .jpg, etc)
@@ -24,8 +24,8 @@
 rows, cols = IM.shape    # image size
 
 # Image center should be mid-pixel, i.e. odd number of colums
-if cols % 2 != 1: 
-    print ("even pixel width image, make it odd and re-adjust image center")
+if cols % 2 != 1:
+    print("even pixel width image, make it odd and re-adjust image center")
     IM = abel.tools.center.center_image(IM, method="slice")
     rows, cols = IM.shape   # new image size
 
@@ -48,7 +48,7 @@
 print('Calculating angular distribution:')
 # radial ranges (of spectral features) to follow intensity vs angle
 # view the speed distribution to determine radial ranges
-r_range = [(93, 111), (145, 162), (255, 280), (330, 350), (350, 370), 
+r_range = [(93, 111), (145, 162), (255, 280), (330, 350), (350, 370),
            (370, 390), (390, 410), (410, 430)]
 
 # map to intensity vs theta for each radial range
@@ -81,8 +81,7 @@
 fig.colorbar(im1, ax=ax1, fraction=.1, shrink=0.9, pad=0.03)
 ax1.set_xlabel('x (pixels)')
 ax1.set_ylabel('y (pixels)')
-ax1.set_title('VMI, inverse Abel: {:d}x{:d}'\
-              .format(rows, cols))
+ax1.set_title('VMI, inverse Abel: {:d}x{:d}'.format(rows, cols))
 
 # Plot the 1D speed distribution
 ax2.plot(speed)
@@ -115,7 +114,7 @@ def PAD(theta, beta, amp):
 ax3.set_title("anisotropy parameter")
 
 
-# Plot the angular distribution 
+# Plot the angular distribution
 plt.tight_layout()
 
 # Save a image of the plot
diff --git a/examples/example_all_O2.py b/examples/example_all_O2.py
index df00ab33..ec3eb150 100644
--- a/examples/example_all_O2.py
+++ b/examples/example_all_O2.py
@@ -24,14 +24,14 @@
 #   dictionary of method: function()
 
 transforms = {
-  "basex": abel.basex.basex_transform,
-  "linbasex": abel.linbasex.linbasex_transform,
-  "direct": abel.direct.direct_transform,
-  "hansenlaw": abel.hansenlaw.hansenlaw_transform,
-  "onion_bordas": abel.onion_bordas.onion_bordas_transform,
-  "onion_dasch": abel.dasch.onion_peeling_transform,
-  "three_point": abel.dasch.three_point_transform,
-  "two_point" : abel.dasch.two_point_transform,
+    "basex": abel.basex.basex_transform,
+    "linbasex": abel.linbasex.linbasex_transform,
+    "direct": abel.direct.direct_transform,
+    "hansenlaw": abel.hansenlaw.hansenlaw_transform,
+    "onion_bordas": abel.onion_bordas.onion_bordas_transform,
+    "onion_dasch": abel.dasch.onion_peeling_transform,
+    "three_point": abel.dasch.three_point_transform,
+    "two_point": abel.dasch.two_point_transform,
 }
 # sort dictionary
 transforms = collections.OrderedDict(sorted(transforms.items()))
@@ -53,7 +53,7 @@
 
 Q0 = Q[0]
 Q0fresh = Q0.copy()    # keep clean copy
-print ("quadrant shape {}".format(Q0.shape))
+print("quadrant shape {}".format(Q0.shape))
 
 # Intensity mask used for intensity normalization
 #   quadrant image region of bright pixels
@@ -70,12 +70,12 @@
 
     Q0 = Q0fresh.copy()   # top-right quadrant of O2- image
 
-    print ("\n------- {:s} inverse ...".format(method))
+    print("\n------- {:s} inverse ...".format(method))
     t0 = time()
 
     # inverse Abel transform using 'method'
     IAQ0 = transforms[method](Q0, direction="inverse", dr=0.1)
-    print ("                    {:.1f} s".format(time()-t0))
+    print("                    {:.1f} s".format(time()-t0))
 
     # polar projection and speed profile
     radial, speed = abel.tools.vmi.angular_integration_3D(IAQ0, origin=(-1, 0),
@@ -96,7 +96,7 @@
 ax0 = plt.subplot2grid((1, 2), (0, 0))
 ax1 = plt.subplot2grid((1, 2), (0, 1))
 
-def ann_plt (quad, subquad, txt):
+def ann_plt(quad, subquad, txt):
     # -ve because numpy coords from top
     annot_angle = -(30+30*subquad+quad*90)*np.pi/180
     annot_coord = (h/2+(h*0.8)*np.cos(annot_angle)/2,
@@ -122,7 +122,7 @@ def ann_plt (quad, subquad, txt):
 
 # reassemble image from transformed (part-)quadrants
 im = abel.tools.symmetry.put_image_quadrants((Q[0], Q[1], Q[2], Q[3]),
-                                              original_image_shape=IModd.shape)
+                                             original_image_shape=IModd.shape)
 
 ax0.axis('off')
 ax0.set_title("inverse Abel transforms")
diff --git a/examples/example_all_Ominus.py b/examples/example_all_Ominus.py
index 475e7015..9ec8901e 100644
--- a/examples/example_all_Ominus.py
+++ b/examples/example_all_Ominus.py
@@ -22,13 +22,13 @@
 #   dictionary of method: function()
 
 transforms = {
-  "direct": abel.direct.direct_transform,      
-  "onion": abel.dasch.onion_peeling_transform, 
-  "hansenlaw": abel.hansenlaw.hansenlaw_transform,
-  "basex": abel.basex.basex_transform,   
-  "three_point": abel.dasch.three_point_transform,
+    "direct": abel.direct.direct_transform,
+    "onion": abel.dasch.onion_peeling_transform,
+    "hansenlaw": abel.hansenlaw.hansenlaw_transform,
+    "basex": abel.basex.basex_transform,
+    "three_point": abel.dasch.three_point_transform,
 }
-# sort dictionary 
+# sort dictionary
 transforms = collections.OrderedDict(sorted(transforms.items()))
 ntrans = np.size(transforms.keys())  # number of transforms
 
@@ -43,8 +43,8 @@
 
 Q0, Q1, Q2, Q3 = abel.tools.symmetry.get_image_quadrants(fIM, reorient=True)
 
-Q0fresh = Q0.copy()    # keep clean copy
-print ("quadrant shape {}".format(Q0.shape))
+Q0fresh = Q0.copy()  # keep clean copy
+print("quadrant shape {}".format(Q0.shape))
 
 # process Q0 quadrant using each method --------------------
 
@@ -58,13 +58,13 @@
 
     Q0 = Q0fresh.copy()   # top-right quadrant of O2- image
 
-    print ("\n------- {:s} inverse ...".format(method))  
+    print("\n------- {:s} inverse ...".format(method))
     t0 = time()
 
     # inverse Abel transform using 'method'
     IAQ0 = transforms[method](Q0, direction="inverse")
 
-    print ("                    {:.1f} s".format(time()-t0))
+    print("                    {:.1f} s".format(time()-t0))
 
     iabelQ.append(IAQ0)  # store for plot
 
@@ -72,14 +72,14 @@
     radial, speed = abel.tools.vmi.angular_integration_3D(IAQ0, origin=(-1, 0))
 
     # normalize image intensity and speed distribution
-    IAQ0 /= IAQ0.max()  
+    IAQ0 /= IAQ0.max()
     speed /= speed.max()
 
     # method label for each quadrant
     annot_angle = -(45+q*90)*np.pi/180  # -ve because numpy coords from top
-    if q > 3: 
-        annot_angle += 50*np.pi/180    # shared quadrant - move the label  
-    annot_coord = (h/2+(h*0.9)*np.cos(annot_angle)/2, 
+    if q > 3:
+        annot_angle += 50*np.pi/180    # shared quadrant - move the label
+    annot_coord = (h/2+(h*0.9)*np.cos(annot_angle)/2,
                    w/2+(w*0.9)*np.sin(annot_angle)/2)
     ax_im.annotate(method, annot_coord, color="yellow")
 
@@ -89,7 +89,7 @@
 # reassemble image, each quadrant a different method
 
 # for < 4 images pad using a blank quadrant
-blank = np.zeros(IAQ0.shape)  
+blank = np.zeros(IAQ0.shape)
 for q in range(ntrans, 4):
     iabelQ.append(blank)
 
@@ -100,11 +100,11 @@
               np.triu(np.flipud(iabelQ[-1]))
     iabelQ[3] = np.flipud(tmp_img)
 # Fix me when > 5 images
- 
+
 
 im = abel.tools.symmetry.put_image_quadrants((iabelQ[0], iabelQ[1],
-                                              iabelQ[2], iabelQ[3]), 
-                                              original_image_shape=IM.shape)
+                                              iabelQ[2], iabelQ[3]),
+                                             original_image_shape=IM.shape)
 
 ax_im.imshow(im, vmin=0, vmax=0.8)
 
diff --git a/examples/example_all_dribinski.py b/examples/example_all_dribinski.py
index 92d64be7..e4f7469a 100644
--- a/examples/example_all_dribinski.py
+++ b/examples/example_all_dribinski.py
@@ -18,25 +18,25 @@
 import matplotlib.pylab as plt
 from time import time
 
-fig, (ax1,ax2) = plt.subplots(1, 2, figsize=(8,4))
+fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(8, 4))
 
 # inverse Abel transform methods -----------------------------
 #   dictionary of method: function()
 
 transforms = {
-  "direct": abel.direct.direct_transform,      
-  "hansenlaw": abel.hansenlaw.hansenlaw_transform,
-  "onion": abel.dasch.onion_peeling_transform,
-  "basex": abel.basex.basex_transform,   
-  "three_point": abel.dasch.three_point_transform,
-  "two_point": abel.dasch.two_point_transform,
+    "direct": abel.direct.direct_transform,
+    "hansenlaw": abel.hansenlaw.hansenlaw_transform,
+    "onion": abel.dasch.onion_peeling_transform,
+    "basex": abel.basex.basex_transform,
+    "three_point": abel.dasch.three_point_transform,
+    "two_point": abel.dasch.two_point_transform,
 }
 
 # sort dictionary:
 transforms = collections.OrderedDict(sorted(transforms.items()))
 # number of transforms:
 ntrans = np.size(transforms.keys())
-  
+
 IM = abel.tools.analytical.SampleImage(n=301, name="dribinski").func
 
 h, w = IM.shape
@@ -47,7 +47,7 @@
 Q0, Q1, Q2, Q3 = abel.tools.symmetry.get_image_quadrants(fIM, reorient=True)
 
 Q0fresh = Q0.copy()    # keep clean copy
-print ("quadrant shape {}".format(Q0.shape))
+print("quadrant shape {}".format(Q0.shape))
 
 # process Q0 quadrant using each method --------------------
 
@@ -57,13 +57,13 @@
 
     Q0 = Q0fresh.copy()   # top-right quadrant of O2- image
 
-    print ("\n------- {:s} inverse ...".format(method))  
+    print("\n------- {:s} inverse ...".format(method))
     t0 = time()
 
     # inverse Abel transform using 'method'
     IAQ0 = transforms[method](Q0, direction="inverse")
 
-    print ("                    {:.4f} s".format(time()-t0))
+    print("                    {:.4f} s".format(time()-t0))
 
     iabelQ.append(IAQ0)  # store for plot
 
@@ -71,15 +71,15 @@
     radial, speed = abel.tools.vmi.angular_integration_3D(IAQ0, origin=(-1, 0))
 
     # normalize image intensity and speed distribution
-    IAQ0 /= IAQ0.max()  
+    IAQ0 /= IAQ0.max()
     speed /= speed.max()
 
     # method label for each quadrant
     annot_angle = -(45+q*90)*np.pi/180  # -ve because numpy coords from top
-    if q > 3: 
-        annot_angle += 50*np.pi/180    # shared quadrant - move the label  
-    annot_coord = (h/2+(h*0.9)*np.cos(annot_angle)/2 -50, 
-                   w/2+(w*0.9)*np.sin(annot_angle)/2)
+    if q > 3:
+        annot_angle += 50*np.pi/180    # shared quadrant - move the label
+    annot_coord = (h/2 + (h*0.9)*np.cos(annot_angle)/2 - 50,
+                   w/2 + (w*0.9)*np.sin(annot_angle)/2)
     ax1.annotate(method, annot_coord, color="yellow")
 
     # plot speed distribution
@@ -88,7 +88,7 @@
 # reassemble image, each quadrant a different method
 
 # for < 4 images pad using a blank quadrant
-blank = np.zeros(IAQ0.shape)  
+blank = np.zeros(IAQ0.shape)
 for q in range(ntrans, 4):
     iabelQ.append(blank)
 
@@ -98,17 +98,17 @@
     tmp_img = np.tril(np.flipud(iabelQ[-2])) +\
               np.triu(np.flipud(iabelQ[-1]))
     iabelQ[3] = np.flipud(tmp_img)
- 
+
 im = abel.tools.symmetry.put_image_quadrants((iabelQ[0], iabelQ[1],
-                                              iabelQ[2], iabelQ[3]), 
-                                              original_image_shape=IM.shape)
+                                              iabelQ[2], iabelQ[3]),
+                                             original_image_shape=IM.shape)
 
 ax1.imshow(im, vmin=0, vmax=0.15)
 ax1.set_title('Inverse Abel comparison')
 
 
 ax2.set_xlim(0, 200)
-ax2.set_ylim(-0.5,2)
+ax2.set_ylim(-0.5, 2)
 ax2.legend(loc=0, labelspacing=0.1, frameon=False)
 ax2.set_title('Angular integration')
 ax2.set_xlabel('Radial coordinate (pixel)')
diff --git a/examples/example_anisotropy_parameter.py b/examples/example_anisotropy_parameter.py
index 19a5daa6..695545d2 100644
--- a/examples/example_anisotropy_parameter.py
+++ b/examples/example_anisotropy_parameter.py
@@ -29,10 +29,11 @@
 # linbasex method - center and center_options ensure image has odd square shape
 LIM = abel.Transform(IM, method='linbasex', origin='slice',
                      center_options=dict(square=True),
-                     transform_options=dict(basis_dir=None,
-                     proj_angles=proj_angles, radial_step=radial_step,
-                     smoothing=smoothing, threshold=threshold, clip=clip,
-                     return_Beta=True, verbose=True))
+                     transform_options=dict(
+                         basis_dir=None, proj_angles=proj_angles,
+                         radial_step=radial_step, smoothing=smoothing,
+                         threshold=threshold, clip=clip, return_Beta=True,
+                         verbose=True))
 
 
 # === Hansen & Law inverse Abel transform ==================
diff --git a/examples/example_dasch_methods.py b/examples/example_dasch_methods.py
index 6498e2cd..fb9240b1 100644
--- a/examples/example_dasch_methods.py
+++ b/examples/example_dasch_methods.py
@@ -21,7 +21,7 @@
 orig_speed = abel.tools.vmi.angular_integration_3D(origQ[0], origin=(-1, 0))
 scale_factor = orig_speed[1].max()
 
-plt.plot(orig_speed[0], orig_speed[1]/scale_factor, linestyle='dashed', 
+plt.plot(orig_speed[0], orig_speed[1]/scale_factor, linestyle='dashed',
          label="Dribinski sample")
 
 
@@ -31,10 +31,11 @@
 # split projected image into quadrants
 Q = abel.tools.symmetry.get_image_quadrants(fIM)
 
-dasch_transform = {\
-"two_point": abel.dasch.two_point_transform,
-"three_point": abel.dasch.three_point_transform, 
-"onion_peeling": abel.dasch.onion_peeling_transform}
+dasch_transform = {
+    "two_point": abel.dasch.two_point_transform,
+    "three_point": abel.dasch.three_point_transform,
+    "onion_peeling": abel.dasch.onion_peeling_transform
+}
 
 for method in dasch_transform.keys():
     Q0 = Q[0].copy()
@@ -43,7 +44,7 @@
 # speed distribution
     speed = abel.tools.vmi.angular_integration_3D(AQ0, origin=(-1, 0))
 
-    plt.plot(speed[0], speed[1]*orig_speed[1][14]/speed[1][14]/scale_factor, 
+    plt.plot(speed[0], speed[1]*orig_speed[1][14]/speed[1][14]/scale_factor,
              label=method)
 
 plt.title("Dasch methods for Dribinski sample image $n={:d}$".format(n))
diff --git a/examples/example_direct_gaussian.py b/examples/example_direct_gaussian.py
index f0e45636..dea1358c 100644
--- a/examples/example_direct_gaussian.py
+++ b/examples/example_direct_gaussian.py
@@ -19,7 +19,7 @@
 
 ref = GaussianAnalytical(n, r_max, sigma, symmetric=False)
 
-fig, ax = plt.subplots(1,2)
+fig, ax = plt.subplots(1, 2)
 
 # forward Abel transform
 reconC = direct_transform(ref.func, dr=ref.dr, direction="forward",
@@ -29,8 +29,8 @@
 
 ax[0].set_title('Forward transform of a Gaussian', fontsize='smaller')
 ax[0].plot(ref.r, ref.abel, label='Analytical transform')
-ax[0].plot(ref.r, reconC , '--', label='correction=True')
-ax[0].plot(ref.r, reconP , ':', label='correction=False')
+ax[0].plot(ref.r, reconC, '--', label='correction=True')
+ax[0].plot(ref.r, reconP, ':', label='correction=False')
 ax[0].set_ylabel('intensity (arb. units)')
 ax[0].set_xlabel('radius')
 
@@ -38,14 +38,14 @@
 # inverse Abel transform
 reconc = direct_transform(ref.abel, dr=ref.dr, direction="inverse",
                           correction=True)
- 
+
 reconnoc = direct_transform(ref.abel, dr=ref.dr, direction="inverse",
-                         correction=False)
+                            correction=False)
 
 ax[1].set_title('Inverse transform of a Gaussian', fontsize='smaller')
 ax[1].plot(ref.r, ref.func, 'C0', label='Original function')
-ax[1].plot(ref.r, reconc , 'C1--', label='correction=True')
-ax[1].plot(ref.r, reconnoc , 'C2:', label='correction=False')
+ax[1].plot(ref.r, reconc, 'C1--', label='correction=True')
+ax[1].plot(ref.r, reconnoc, 'C2:', label='correction=False')
 ax[1].set_xlabel('radius')
 
 for axi in ax:
diff --git a/examples/example_hansenlaw.py b/examples/example_hansenlaw.py
index 2a945c5e..92920ea3 100644
--- a/examples/example_hansenlaw.py
+++ b/examples/example_hansenlaw.py
@@ -9,7 +9,7 @@
 import bz2
 
 # Hansen and Law inverse Abel transform of velocity-map imaged electrons
-# from O2- photodetachement at 454 nm. The spectrum was recorded in 2010  
+# from O2- photodetachement at 454 nm. The spectrum was recorded in 2010
 # at the Australian National University (ANU)
 # J. Chem. Phys. 133, 174311 (2010) DOI: 10.1063/1.3493349
 
@@ -29,7 +29,7 @@
 AIM = abel.Transform(IMc, method='hansenlaw',
                      use_quadrants=(True, True, True, True),
                      symmetry_axis=None,
-                     transform_options=dict(dr=0.5, align_grid=False), 
+                     transform_options=dict(dr=0.5, align_grid=False),
                      angular_integration=True,
                      angular_integration_options=dict(dr=0.5),
                      verbose=True)
diff --git a/examples/example_hansenlaw_Xe.py b/examples/example_hansenlaw_Xe.py
index 63f04439..c4c4b688 100644
--- a/examples/example_hansenlaw_Xe.py
+++ b/examples/example_hansenlaw_Xe.py
@@ -12,10 +12,10 @@
 import scipy.misc
 
 # This example demonstrates Hansen and Law inverse Abel transform
-# of an image obtained using a velocity map imaging (VMI) photoelecton 
-# spectrometer to record the photoelectron angular distribution resulting 
+# of an image obtained using a velocity map imaging (VMI) photoelecton
+# spectrometer to record the photoelectron angular distribution resulting
 # from photodetachement of O2- at 454 nm.
-# This spectrum was recorded in 2010  
+# This spectrum was recorded in 2010
 # ANU / The Australian National University
 # J. Chem. Phys. 133, 174311 (2010) DOI: 10.1063/1.3493349
 
@@ -29,18 +29,18 @@
 
 print('Loading ' + filename)
 #im = np.loadtxt(filename)
-im = plt.imread(filename) 
-(rows,cols) = np.shape(im)
-print ('image size {:d}x{:d}'.format(rows,cols))
+im = plt.imread(filename)
+(rows, cols) = np.shape(im)
+print('image size {:d}x{:d}'.format(rows, cols))
 
 
 # Step 2: perform the Hansen & Law transform!
 print('Performing Hansen and Law inverse Abel transform:')
 
-recon = abel.Transform(im, method="hansenlaw", direction="inverse", 
-                       symmetry_axis=None, verbose=True, 
-                       origin=(240,340)).transform
-                       
+recon = abel.Transform(im, method="hansenlaw", direction="inverse",
+                       symmetry_axis=None, verbose=True,
+                       origin=(240, 340)).transform
+
 r, speeds = abel.tools.vmi.angular_integration_3D(recon)
 
 # Set up some axes
diff --git a/examples/example_hansenlaw_basex.py b/examples/example_hansenlaw_basex.py
index cd69095c..15a603a6 100644
--- a/examples/example_hansenlaw_basex.py
+++ b/examples/example_hansenlaw_basex.py
@@ -44,18 +44,18 @@
 print("scaling image to size 501 reduce the time of the basis set calculation")
 im = zoom(im, 0.4892578125)
 (rows, cols) = np.shape(im)
-if cols%2 == 0:
-    print ("Even pixel image cols={:d}, adjusting image centre\n",
-           " center_image()".format(cols))
+if cols % 2 == 0:
+    print("Even pixel image cols={:d}, adjusting image centre\n",
+          " center_image()".format(cols))
     im = abel.tools.center.center_image(im, center="slice", odd_size=True)
     # alternative
-    #im = shift(im,(0.5,0.5))
+    #im = shift(im,(0.5, 0.5))
     #im = im[:-1, 1::]  # drop left col, bottom row
-    (rows,cols) = np.shape(im)
+    (rows, cols) = np.shape(im)
 
 c2 = cols//2   # half-image width
 r2 = rows//2   # half-image height
-print ('image size {:d}x{:d}'.format(rows,cols))
+print('image size {:d}x{:d}'.format(rows, cols))
 
 # Hansen & Law inverse Abel transform
 print('Performing Hansen and Law inverse Abel transform:')
@@ -86,11 +86,11 @@
 ax1.set_title('velocity map image: size {:d}x{:d}'.format(rows, cols))
 
 # Plot the 2D transform
-reconH2 = reconH[:,:c2]
-reconB2 = reconB[:,c2:]
-recon = np.concatenate((reconH2,reconB2), axis=1)
+reconH2 = reconH[:, :c2]
+reconB2 = reconB[:, c2:]
+recon = np.concatenate((reconH2, reconB2), axis=1)
 im2 = ax2.imshow(recon, origin='lower', vmin=0,
-                 vmax=recon[:r2-50,:c2-50].max())
+                 vmax=recon[:r2-50, :c2-50].max())
 fig.colorbar(im2, ax=ax2, fraction=0.1, shrink=0.9, pad=0.03)
 ax2.set_xlabel('x (pixels)')
 ax2.set_ylabel('y (pixels)')
diff --git a/examples/example_linbasex.py b/examples/example_linbasex.py
index 190a74f8..f2556026 100644
--- a/examples/example_linbasex.py
+++ b/examples/example_linbasex.py
@@ -11,27 +11,27 @@
 import matplotlib.pylab as plt
 
 # This example demonstrates ``linbasex`` inverse Abel transform
-# of a velocity-map image of photoelectrons from O2- photodetachment at 454 nm. 
+# of a velocity-map image of photoelectrons from O2- photodetachment at 454 nm.
 # Measured at  The Australian National University
 # J. Chem. Phys. 133, 174311 (2010) DOI: 10.1063/1.3493349
 
-# Load image as a numpy array - numpy handles .gz, .bz2 
+# Load image as a numpy array - numpy handles .gz, .bz2
 imagefile = bz2.BZ2File('data/O2-ANU1024.txt.bz2')
 IM = np.loadtxt(imagefile)
 
 if os.environ.get('READTHEDOCS', None) == 'True':
-    IM = IM[::2,::2]
+    IM = IM[::2, ::2]
 # the [::2, ::2] reduces the image size x1/2, decreasing processing memory load
 # for the online readthedocs.org
 
-# Image center should be mid-pixel and the image square, 
+# Image center should be mid-pixel and the image square,
 # `center=convolution` takes care of this
 
 un = [0, 2]  # spherical harmonic orders
-proj_angles = np.arange(0, 2*np.pi, np.pi/20) # projection angles
+proj_angles = np.arange(0, 2*np.pi, np.pi/20)  # projection angles
 # adjust these parameter to 'improve' the look
-smoothing = 0.9  # smoothing Gaussian 1/e width
-threshold = 0.01 # exclude small amplitude Newton spheres
+smoothing = 0.9   # smoothing Gaussian 1/e width
+threshold = 0.01  # exclude small amplitude Newton spheres
 # no need to change these
 radial_step = 1
 clip = 0
@@ -44,7 +44,7 @@
                                             proj_angles=proj_angles,
                                             smoothing=smoothing,
                                             radial_step=radial_step, clip=clip,
-                                            threshold=threshold)) 
+                                            threshold=threshold))
 
 # angular, and radial integration - direct from `linbasex` transform
 # as class attributes
@@ -63,7 +63,7 @@
 # join 1/2 raw data : 1/2 inversion image
 inv_IM = LIM.transform
 cols = inv_IM.shape[1]
-c2 = cols//2 
+c2 = cols//2
 vmax = IM[:, :c2-100].max()
 inv_IM *= vmax/inv_IM[:, c2+100:].max()
 JIM = np.concatenate((IM[:, :c2], inv_IM[:, c2:]), axis=1)
diff --git a/examples/example_linbasex_hansenlaw.py b/examples/example_linbasex_hansenlaw.py
index 4ac89d3f..89d84c5b 100644
--- a/examples/example_linbasex_hansenlaw.py
+++ b/examples/example_linbasex_hansenlaw.py
@@ -10,16 +10,17 @@
 radial_step = 1  # pixel grid
 smoothing = 1  # smoothing 1/e-width for Gaussian convolution smoothing
 threshold = 0.2  # threshold for normalization of higher order Newton spheres
-clip=0  # clip first vectors (smallest Newton spheres) to avoid singularities
+clip = 0  # clip first vectors (smallest Newton spheres) to avoid singularities
 
 # linbasex method - center ensures image has odd square shape
 #                 - speed and anisotropy parameters evaluated by method
 LIM = abel.Transform(IM, method='linbasex', origin='convolution',
                      center_options=dict(square=True),
-                     transform_options=dict(basis_dir=None,
-                     proj_angles=proj_angles, radial_step=radial_step,
-                     smoothing=smoothing, threshold=threshold, clip=clip,
-                     return_Beta=True, verbose=True))
+                     transform_options=dict(
+                         basis_dir=None, proj_angles=proj_angles,
+                         radial_step=radial_step, smoothing=smoothing,
+                         threshold=threshold, clip=clip, return_Beta=True,
+                         verbose=True))
 
 # hansenlaw method - speed and anisotropy parameters evaluated by integration
 HIM = abel.Transform(IM, method="hansenlaw", origin='convolution',
@@ -32,10 +33,10 @@
       abel.tools.vmi.radial_integration(HIM.transform, radial_ranges=rrange)
 
 plt.figure(figsize=(12, 6))
-ax0 = plt.subplot2grid((2,4), (0,0))
-ax3 = plt.subplot2grid((2,4), (1,0))
-ax1 = plt.subplot2grid((2,4), (0,1), colspan=2, rowspan=2)
-ax2 = plt.subplot2grid((2,4), (0,3), sharex=ax1, rowspan=2)
+ax0 = plt.subplot2grid((2, 4), (0, 0))
+ax3 = plt.subplot2grid((2, 4), (1, 0))
+ax1 = plt.subplot2grid((2, 4), (0, 1), colspan=2, rowspan=2)
+ax2 = plt.subplot2grid((2, 4), (0, 3), sharex=ax1, rowspan=2)
 
 ax0.imshow(LIM.transform, vmin=0, vmax=LIM.transform.max()*2/3)
 ax0.set_aspect('equal')
diff --git a/examples/example_rbasex_multimass.py b/examples/example_rbasex_multimass.py
index 7c25ed0f..79daf6c4 100644
--- a/examples/example_rbasex_multimass.py
+++ b/examples/example_rbasex_multimass.py
@@ -111,7 +111,7 @@
 plt.subplot(223)
 plt.title('Distributions: central image')
 # here the default order=2 is needed and used
-_, distr = rbasex_transform(im, origin=origin2, rmax=r2,weights=mask2, out=None)
+_, distr = rbasex_transform(im, origin=origin2, rmax=r2, weights=mask2, out=None)
 r, I, beta = distr.rIbeta()
 plt.plot(r, I, c=rgb(1), label='$I(r)$')
 # beta(r) I(r) is the "speed distribution" of P_2(r)
diff --git a/examples/example_simple.py b/examples/example_simple.py
index 519b7510..759c42d5 100644
--- a/examples/example_simple.py
+++ b/examples/example_simple.py
@@ -1,7 +1,7 @@
 import abel
-original     = abel.tools.analytical.SampleImage().func
-forward_abel = abel.Transform(original, direction='forward', 
-                              method='hansenlaw'  ).transform
+original = abel.tools.analytical.SampleImage().func
+forward_abel = abel.Transform(original, direction='forward',
+                              method='hansenlaw').transform
 inverse_abel = abel.Transform(forward_abel, direction='inverse',
                               method='three_point').transform
 


Test patch:
diff --git a/abel/tests/test_tools_analytical.py b/abel/tests/test_tools_analytical.py
index 4099102f..52bbc108 100644
--- a/abel/tests/test_tools_analytical.py
+++ b/abel/tests/test_tools_analytical.py
@@ -65,6 +65,24 @@ def test_sample_Gaussian():
     assert_allclose(test.abel, np.sqrt(np.pi) * sigma * ref)
 
 
+def test_sample_Gerber():
+    """
+    Test SampleImage 'Gerber'.
+    """
+    # test transform with forward Daun with cubic splines (the most accurate)
+    test = SampleImage(n=513, name='Gerber')  # original resolution, rmax = 256
+    proj = Transform(test.func, method='daun', direction='forward',
+                     symmetry_axis=(0, 1),
+                     transform_options={'degree': 3,
+                                        'verbose': False}).transform
+    assert_allclose(proj, test.abel, atol=6e-3 * np.max(proj))
+
+    # test sigma-independence of total intensity
+    test1 = SampleImage(name='Gerber', sigma=1)
+    test2 = SampleImage(name='Gerber', sigma=2)
+    assert_allclose(test1.abel.sum(), test2.abel.sum(), rtol=1e-3)
+
+
 def test_sample_O2():
     """
     Test SampleImage 'O2'.
@@ -111,5 +129,6 @@ def test_sample_Ominus():
 if __name__ == "__main__":
     test_sample_Dribinski()
     test_sample_Gaussian()
+    test_sample_Gerber()
     test_sample_O2()
     test_sample_Ominus()