rasterio / affine Goto Github PK
View Code? Open in Web Editor NEWAffine transformation matrices
License: BSD 3-Clause "New" or "Revised" License
Affine transformation matrices
License: BSD 3-Clause "New" or "Revised" License
See #32.
Instances of affine.Affine()
can be pickled, but they can't be unpickled, which means they can't be used with modules like multiprocessing
because elements 7, 8, and 9 are given to __new__
when the object is unpickled.
Here's an example:
from multiprocessing import Pool
import affine
def processor(x):
assert isinstance(x, affine.Affine)
return x
a1 = affine.Affine(1, 2, 3, 4, 5, 6)
a2 = affine.Affine(6, 5, 4, 3, 2, 1)
results = Pool(1).map(processor, [a1, a2])
for expected, actual in zip([a1, a2], results):
assert expected == actual
Process PoolWorker-1:
Traceback (most recent call last):
File "/usr/local/Cellar/python/2.7.9/Frameworks/Python.framework/Versions/2.7/lib/python2.7/multiprocessing/process.py", line 258, in _bootstrap
self.run()
File "/usr/local/Cellar/python/2.7.9/Frameworks/Python.framework/Versions/2.7/lib/python2.7/multiprocessing/process.py", line 114, in run
self._target(*self._args, **self._kwargs)
File "/usr/local/Cellar/python/2.7.9/Frameworks/Python.framework/Versions/2.7/lib/python2.7/multiprocessing/pool.py", line 102, in worker
task = get()
File "/usr/local/Cellar/python/2.7.9/Frameworks/Python.framework/Versions/2.7/lib/python2.7/multiprocessing/queues.py", line 376, in get
return recv()
File "/usr/local/lib/python2.7/site-packages/affine/__init__.py", line 152, in __new__
"Expected 6 coefficients, found %d" % len(members))
TypeError: Expected 6 coefficients, found 9
$ pip freeze | grep rasterio
rasterio==1.0.25
$ pip freeze | grep affine
affine==2.2.2
PyCharm warns that Affine
expects a List[float]
but it doesn't work that way, it works with individual float
arguments (see screenshot attached). Is the typing markup out of sync with the function arguments?
Don't understand how PyCharm generates that warning from the class definition, maybe something related to this doc-string?
If an actual List[float]
is provided, the TypeError
is triggered because len(members) == 1
The module-level global is problematic. As outlined here: rasterio/rasterio#430 (comment)
>>> import affine as affineA
>>> import affine as affineB
>>> affineA.EPSILON
1e-05
>>> affineB.EPSILON
1e-05
>>> instA = affineA.Affine(0.001215, 0.0, -120.9375, 0.0, -0.001215, 38.823)
>>> instA.is_degenerate
True
>>> affineA.set_epsilon(1e-20)
>>> instA.is_degenerate # doesn't change existing instances
True
>>> instB = affineB.Affine(0.001215, 0.0, -120.9375, 0.0, -0.001215, 38.823)
>>> instB.is_degenerate # but it does change new instances, even from different module names
False
>>> affineB.EPSILON
1e-20
>>> affineA.EPSILON
1e-20
Three thoughts
Affine.adjust_epsilon_assume_invertable()
method as a convenience method to check for invertability and bump up the epsilon for that instance under the assumption that it is invertable. Similarly we could have a flag to turn off is_degenerate checks for an instance.1e-40
?The almost-degenerate test for whether a transform is invertible isn't working for Rasterio. I propose to make a 1.3.0 release in which the is_degenerate
predicate evaluates self.determinant == 0.0
. That would be the only change.
Target: mid-April.
/cc @perrygeo
We will have a 2.3 release to announce the deprecation of right multiplication like (1, 1) * Affine.scale(2)
(see #32).
It would be good if WHLs could be generated for Windows, since it is a lot less likely people have a compiler there.
Remove python 2.7 from the build matrix and import Sequence only from collections.abc.
TODO:
@caseman this Affine package is derived from your Planar package. Affine
is a beautiful class and super useful for my GIS raster data package, Rasterio. Is the attribution in README.md and affine/init.py to your satisfication?
I'm getting degenerate affine transformations for images with a very small cell size, like Landsat scenes in WGS84. Is 1-e5
the default because it makes sense for mathematics? I know there is a set_epsilon()
function to manage this variable but wanted to get your thoughts about this before I dug deeper.
Here's an example of a Landsat scene going from UTM -> WGS84 for anyone else that might encounter this issue. Affine.determinant
and affine.EPSILON
are used in Affine.is_degenerate
.
cc/ @Yolandeezy
Check the raw data
Captain:BAD kwurster$ rio insp LC80150332015197LGN00_B5.TIF
Rasterio 0.25.0 Interactive Inspector (Python 2.7.10)
Type "src.meta", "src.read(1)", or "help(src)" for more information.
>>> src.affine
Affine(30.0, 0.0, 220485.0,
0.0, -30.0, 4426815.0)
>>> ~src.affine
Affine(0.03333333333333333, 0.0, -7349.5,
0.0, -0.03333333333333333, 147560.5)
>>> exit()
Reproject to WGS84
Captain:BAD kwurster$ rio warp LC80150332015197LGN00_B5.TIF warped.tif --dst-crs EPSG:4326
Check the reprojected data
Captain:BAD kwurster$ rio insp warped.tif
Rasterio 0.25.0 Interactive Inspector (Python 2.7.10)
Type "src.meta", "src.read(1)", or "help(src)" for more information.
>>> src.affine
Affine(0.0003139959197432608, 0.0, -78.27163078313896,
0.0, -0.0003139959197432608, 39.99035001263342)
>>> ~src.affine
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "/usr/local/lib/python2.7/site-packages/affine/__init__.py", line 423, in __invert__
"Cannot invert degenerate transform")
TransformNotInvertibleError: Cannot invert degenerate transform
>>> src.affine.determinant
-9.859343761541627e-08
>>> import affine
>>> affine.EPSILON
1e-05
Not sure if this really belongs here but in keeping with the spirit of to_gdal
and from_gdal
methods, it might be good to provide wrappers for the World File ordering: a, d, b, e, c*, f*
The only complication is that c*
and f*
refer to the center of the upper left cell so we'd need to pad them by 1/2 a
and e
.
A recent change (dask/dask#9361) in how dask handles arguments that are instances or subclasses of namedtuple
prevents Affine
objects from being passed to delayed operations such as dask.array.map_blocks
. Dask now unpacks the namedtuples and then tries to repack them before providing them to the delayed function at compute time. This breaks for Affine objects since they unpack to the full 9 coefficients but Affine.__new__
only takes the first 6 coefficients. The result is
TypeError: __new__() takes 7 positional arguments but 10 were given
when dask graphs are computed.
My current solution is to pass the coefficients in place of the instance but it would be great to simply pass Affine objects again.
Background: this affine package is used in Rasterio to represent the affine transformation matrix for georeferenced raster data (satellite imagery, etc).
Geospatial raster data rarely has rotation in the matrix (not never, but rarely) so the determinant of the matrix is most often equal to the product of the raster's pixel width and height. When the raster's georeferencing is in units of meters or feet, its transform will be invertible using affine 1.2.0. However, the same class of raster data – Landsat 8 with 30 meter pixels, say – in a geographic projection with units of decimal degrees (30 meters ~= 0.0003 degrees) will have transform determinants on the order of 1e-7. They're perfectly invertible but affine 1.2.0 says no unless you adjust the global EPSILON value.
It's common in Rasterio to be working with one transform that has a determinant on the order of -1000 and another with a determinant on the order of -1e-7 and both of them invertible. Juggling the global EPSILON is error prone. I propose to make the is_degenerate
predicate exact and not approximate, replacing EPSILON with 0.
For developers using docker in more restricted environments having manylinux WHLs available would be good.
Because Travis-CI isn't free any more. Following example in https://hynek.me/articles/python-github-actions.
New feature: permutation #35 .
Bug fix (#4).
Breaking changes like removal of __rmul__
.
I assume that the correct behaviour is that they do
I think that most people who are using affine
will also be using rasterio
and, thus, working with numpy
arrays. It would be nice to be able to transparently batch convert numpy arrays of coordinates with just anAffine * aNumpyArray
.
As far as I know, it's standard to have lists of coordinates in numpy arrays shaped [..., 2]
because it makes operations like "add (2, 1) to all coordinates" a very simple numpy operation (arr + (2, 1)
).
This is what I would like to work:
import affine
import numpy as np
arr = np.array([(1, 1), (4, 6), (2, 5), (3, 1)])
aff = affine.Affine(2, 0, 3, 0, 3, 1) # scale up by (2, 3), offset by (3, 1)
print(arr * aff)
There is a work-around: you can transpose the input, and combine back into a numpy array with:
>>> print(np.array(aff * arr.T).T)
[[ 5. 4.]
[11. 19.]
[ 7. 16.]
[ 9. 4.]]
But, I think it would be much nicer if aff * arr
was just supported to do this operation automatically.
Why my metrix is rounded automatically?
print(Affine.rotation(45.0))
# I got:
# | 0.71,-0.71, 0.00|
# | 0.71, 0.71, 0.00|
# | 0.00, 0.00, 1.00|
In Affine, rotations are always in degrees, but it can sometimes be useful to expose radians.
If you are considering changing the API, would you consider exposing an API that works in radians ?
I had a quick look and on ubuntu there is python3-affine
, and at least on rpm distros there is a package of the same name, so things are looking pretty good for linux outside of pip (which is pretty big for beginners).
On the mac side I couldn't find Affine on homebrew, would it be possible to add a formula for it there ?
Bug fix: #40
Not charactizing affine transformation correctly. Sorry. Closed.
Hi Affine team,
Very much enjoy using this library via rasterio.
I am pretty sure this is purely a floating point issue, but wanted to post to get clarity. Works as expected!
In [1]: from affine import Affine
In [2]: t = Affine(0.00041666666666666664, 0.0 , -181.00020833333335, 0.0, -0.0002777777777777778, 51.75013888888889)
In [3]: t * (.5, .5)
Out[3]: (-181.0, 51.75)
In [4]: -181.00020833333335 + 0.00041666666666666664 * .5
Out[4]: -180.0
In [5]: 51.75013888888889 - 0.0002777777777777778 * .5
Out[5]: 51.75
It appears the transform is rounding to 4 digits.
I wanted to use Pydantic validators for making data types. However, it does not work properly in Affine 2.3.1:
from pydantic import BaseModel
from affine import identity
class Transform(BaseModel):
transform: Affine
Transform(transform=identity)
This returns the following error:
---------------------------------------------------------------------------
ValidationError Traceback (most recent call last)
<ipython-input-45-0e11ed9741f8> in <cell line: 1>()
----> 1 Transform(transform=identity)
~/anaconda3/envs/mapOsirisv2/lib/python3.10/site-packages/pydantic/main.cpython-310-x86_64-linux-gnu.so in pydantic.main.BaseModel.__init__()
ValidationError: 1 validation error for Transform
transform
Affine.__new__() got an unexpected keyword argument 'g' (type=type_error)
This is similar to issue #79 .
I'm trying to implement __rmul__
for a custom type that takes Affine object on the left A * g
--> g.__rmul__(A)
, this works fine except when g
is iterable containing exactly two elements. Since then A.__mul__(g)
goes into code path listed below and fails with ValueError
Lines 512 to 516 in 78c20a0
I think return statement on line 516 above should be moved inside the try block.
Planar had some nice HTML docs
https://pythonhosted.org/planar/transforms.html
I'm not sure how the above docs are generated, if they are something standard it may be worth moving docs to readthedocs.
It would be good to get these for Affine, it may be a matter of contacting Casey, I know a number of years ago he was pretty keen for someone to pick up Planar, so don't imagine this would be a problem.
Hi,
I've been working with the GUF recently, and have been making heavy uses of windows & transforms, so forgive me if this I'm just new or this is anticipated.
I'm in a situation where I can make a view of the GUF (a 2.8 arcsecond global raster, here defined in a vrt)
import rasterio as rio
handler = rio.open('/mnt/data/guf/GUF28_v2/GUF28_DLR_v01_global.vrt')
Then, build a window & read for Bristol, England:
bristol_window = handler.window(-2.75,51.25,-2.25,51.75)
bristol_transform = handler.window_transform(bristol_window)
bristol = handler.read(window=bristol_window)
I do some sklearn
magic to derive labels_
, which gives an additional classification for each pixel
labels_ = classifier.fit(bristol)
and was intending to map that alongside the bristol boundary, using the transforms:
to_longlat = lambda row,col: (col,row) * bristol_transform * rio.Affine(.5,.5)
Applying this over my window gives me a slightly-incorrect adjustment:
but, switching to
# force affine combination before hitting the row-col
to_longlat = lambda row,col: (col,row) * (bristol_transform * rio.Affine(.5,.5))
Is this intentional? I usually expect *
to be undirected and @
to be directed, but maybe I'm just missing what's implied by multiplication here.
For new non-breaking changes like #92 and others.
The rasterio.transform
module has several methods for generating an instance of affine.Affine
. I don't think these methods are specific to rasterio, and given that they just call Affine
class methods, I think it would be useful to define these here.
If you think this is a good idea I'm happy to write up a PR!
Rasterio has some poor affine usage in one function, computing position * matrix
. I've fixed that, but even though matrix * position
is more proper we need to bring __rmul__
back in case others are depending on it.
Understand there are three additional parameters, perhaps a brief note (I may have missed) would be of use.
• a = width of a pixel
• b = row rotation (typically zero)
• c = x-coordinate of the upper-left corner of the upper-left pixel
• d = column rotation (typically zero)
• e = height of a pixel (typically negative)
• f = y-coordinate of the of the upper-left corner of the upper-left pixel
I am using the affine client, I don't know if it is a problem with the client, or Affine itself.
Tried on different computers with different scroll settings, but the result is the same.
Vertical scrolling works at the correct speed.
OS: Windows 11
👋
I saw mapbox/mercantile#140 and since @sgillies was receptive to the idea of adding type hints, I thought I'd also start a discussion here about adding type hints 🙂 .
Affine is a relatively small and self-contained library, so it could be a good library in the ecosystem to start with. I'd be happy to put up a PR if there's interest.
One possible hiccup that I found in the creation of external type stubs for affine is that mypy complained about violating the Liskov substitution principle. Specifically I wasn't able to type __mul__
to take an Affine
object as input and output, because that's an incompatible override of the NamedTuple's __mul__
typing.
Shapely's affinity module expects a tuple with the (a, b, d, e, xoff, yoff)
order. I suggest adding a method to the Affine
class to improve interoperability with shapely.
Let me know if that's an interesting proposal and I'll send a PR.
Loïc
Because of how __rmul__
is implemented, left multiplication can give surprising results:
>>> Affine.translation(1, 1) * Affine.scale(2, 3) * (1, 1)
(3.0, 4.0)
>>> (Affine.translation(1, 1) * Affine.scale(2, 3)) * (1, 1)
(3.0, 4.0)
>>> (1, 1) * Affine.translation(1, 1) * Affine.scale(2, 3)
(4.0, 6.0)
>>> (1, 1) * (Affine.translation(1, 1) * Affine.scale(2, 3))
(3.0, 4.0)
I think you already anticipated this:
https://github.com/sgillies/affine/blob/2.1.0/affine/__init__.py#L470-L472
However, Shapely shapely.affinity.affine_transform
performs left multiplication and uses the GDAL order, so trying to mix these two can be a pain to get right. I think this should be at least document, but perhaps better raise a warning or even an exception.
Pickling and a bug fix.
As discussed several months ago on rasterio#80, I thought it would be convenient to read/write world files, which use the same 6 coefficients as Affine.
Any ideas for names, prototypes, behaviour? The reader will certainly be another @classmethod
that returns an Affine object. Initially, I was thinking from_worldfile(fp)
, but it could also be load_worldfile(fp)
and/or loads_worldfile(s)
for reading. Similarly, dump_worldfile(fp)
and/or dumps_worldfile()
for writing.
Looking at the shear
classmethod, I'm pretty sure the sx
and sy
coefficients are in the wrong place of the affine transformation matrix, and need to be swapped.
See, for example here (where ShearX changes m01
and ShearY changes m10
. Or also on Wikipedia. The skew
method for Shapely has the x and y shears in the correct place.
For continuous integration testing. Using pytest.
I believe that itransform
is incorrect for Affine matrices that have rotation/shear
I assume that A.itransform(pts)
is equivalent to:
pts = [A*pt for pt in pts]
It's not for affine transforms that have non-zero off-diagonal entries.
I think on this line sd <-> sb
are swapped
https://github.com/sgillies/affine/blob/b125a6397c2a0ff4d7587a43b8678ee5f54765ed/affine/__init__.py#L519
Unit test only checks with scale only matrix.
To make remote password rotation possible.
I am not sure but I think this is outdated code for python2 and 3 compatibility?
Lines 61 to 78 in 2886707
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.