1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362
|
#!/usr/bin/env python
"""This script builds the documentation about migrating
from Gamera 2.x to Gamera 3.x. End users should not
have to run this script."""
import cStringIO
import imp
import os
import re
import textwrap
# This is the main content of the document with Python
# formatting markers in it for parts that are
# automatically generated.
source = """=======================================
Migrating from Gamera 2.x to Gamera 3.x
=======================================
The migration from Gamera 2.x to Gamera 3.x is intended to be as
painless as possible.
In future releases of Gamera, backward compatibility will need to be
broken in order to fix some low-level design flaws in Gamera. Gamera
3.x is the first step in that process. Certain functions signatures
that existed in 2.x have been deprecated in 3.x, but they continue to
work in order to maintain backward compatibility with existing
scripts. As of Gamera 3.x, these deprecated calls all have easy
alternatives, and they should be replaced with the new recommended
forms as soon as possible to ensure compatibility with future versions
of Gamera.
Note, however, that some rarely-used deprecated functions do not have
direct alternatives in Gamera 2.x, so this migration process may break
your scripts' compatibility with Gamera 2.x. However, if appropriate
care is taken, such as switching based on the Gamera version, it
should still be possible to write code that is compatible with both
Gamera 2.x and Gamera 3.x.
This document is divided into the following sections:
- `Reasons for deprecations`_ describes the different categories of
deprecated functions, and what changes are required in end-user
scripts.
- `How to migrate existing code`_ presents some tips and
techniques for making migration easier.
- `Migration tools`_ describes the provided tools for finding and
replacing deprecated calls in end-user code. This tools provide
only a semi-automated process.
- `C++ deprecations reference`_ and `Python deprecations reference`_
list all deprecated functions, with their reason for deprecation
and a suggested alternative.
Reasons for deprecations
========================
(x, y) coordinate consistency
-----------------------------
In Gamera 2.x, some functions received coordinates in the order (y, x),
(or (rows, cols)), while others took coordinates in (x, y) order.
This self-inconsistency and departure from the majority of image
processing systems often resulted in confusion and subtle errors.
The new recommended way to call these functions is to pass in Point,
FloatPoint, Size or Dim arguments as required, instead of two
integers. This solution allows the old function signatures to be
maintained for backward compatibility, while allowing migration to a
style that consistently uses (x, y) ordering everywhere.
For example, ``image.get(r, c)`` becomes ``image.get(Point(c, r))``.
2-element sequences in place of Point type
''''''''''''''''''''''''''''''''''''''''''
For convenience in Python code, 2-element sequences can be used
wherever Point or FloatPoint is expected. Therefore, ``image.get((x, y))``
is equivalent to ``image.get(Point(x, y))``.
Dimensions type
'''''''''''''''
Additionally, the ``Dimensions`` class, whose constructor is
``Dimensions(nrows, ncols)``, has been deprecated because it is
inconsistent with the new requirement of "(x, y) everywhere". Since
it would be impossible to change the order of the constructor's
arguments without breaking backward imcompatibility, a new type has
been introduced, ``Dim``, which is identical to Dimensions in all
respects except its constructor is ``Dim(ncols, nrows)``. All uses of
the ``Dimensions`` type are deprecated and should be migrated to use ``Dim``
instead.
FloatPoint type
'''''''''''''''
A new FloatPoint type has been added to hold coordinates using
floating point numbers. The standard Gamera ``Point`` stores
coordinates as unsigned (positive) integers, and doesn't have any
arithmetic operators. For this reason, ``FloatPoint`` is highly
recommended for any analyses that require precision and flexibility.
``Point`` is kept around for backward compatibility, and because it is
a more natural way to refer to physical pixels, as opposed to logical
coordinates.
There are, however, implicit conversions (in Python only) between the
two types, so a ``FloatPoint`` can be used in place of ``Point`` where
it makes sense. Care should be taken to ensure that negative values
are never used to reference image pixels. (Range checking is
performed when accessing from Python, but not when accessing from C++.)
Additionally, the standard arithmetic operators are available on
``FloatPoint`` objects (+ - * / abs).
Functions should be parameterized by arguments, not by name
-----------------------------------------------------------
There are certain groups of plugin functions that perform essentially
the same functionality. Take for example::
black_horizontal_run_histogram()
black_vertical_run_histogram()
white_horizontal_run_histogram()
white_vertical_run_histogram()
These four functions compute a run length histogram, parameterized by
the color and direction of the runs. Maintaining
four separate functions for a single logical task has a number
of disadvantages:
- Even if the code is abstracted such that the core of the algorithm
is in a single function, the documentation still needs to be
updated in multiple places.
- The generated documentation becomes longer and therefore harder to browse
through, and contains a lot of redundant information or excessive
hyperlinking.
- Autocompletion in the Gamera shell becomes less useful.
- Alphabetization of the functions doesn't necessarily reveal their
membership as part of the same family of functionality.
Therefore, in Gamera 3.x, these sorts of functions have been merged
into a single function. For example, the four functions above are now
the single function::
run_histogram(color, direction)
How to migrate existing code
============================
There are two distinct techniques for finding deprecated functions:
one for C++ code and one for Python code.
C++ code
--------
On the C++ side, all deprecated function calls will be caught at
compile time. Simply recompiling your C++ code will provide compiler
warnings to this effect.
The compiler warnings produced by gcc can be fairly cryptic. The
gamera_deprecation_filter_ (described below) will filter these warning
messages and produce detailed human-readable suggestions for updating
your deprecated calls.
.. note::
This technique only works with gcc version 3.1 and greater.
Python code
-----------
Finding the deprecated calls in Python code is somewhat more
difficult, since the exact function calls being made can not be
determined until runtime.
When a deprecated function call is made, a deprecation warning is
printed to stderr. This message provides the name of the deprecated
function, the reason it was deprecated, and a suggested alternative.
The only way to find all deprecated calls in your code this is to
ensure that all of your code is run. This is sometimes difficult to
do, though a code coverage tool such as Garth Rees' `Statement
coverage for Python`__ may help.
.. __: http://www.garethrees.org/2001/12/04/python-coverage/
A manual search through your code for deprecated functions may in some
cases be more efficient. A master list of all deprecated Python
functions in Gamera is presented in the `Python deprecations reference`_.
Migration tools
===============
There are a number of scripts in the ``migration_tools`` directory
that make the process of migrating code from Gamera 2.x to 3.x easier.
- gamera_deprecation_filter_: Helps display the deprecated calls in
your C++ code.
- replace_get_set_: Replaces C++ calls to get and set in the old
form to the new form.
gamera_deprecation_filter
-------------------------
%(gamera_deprecation_filter_docs)s
replace_get_set
---------------
%(replace_get_set_docs)s
C++ deprecations reference
==========================
This is an alphabetical list of the deprecated C++ functions.
%(cpp_deprecations)s
Python deprecations reference
=============================
This is an alphabetical list of the deprecated Python functions.
%(python_deprecations)s
"""
# This is a list of reasons for deprecating functions and
# their
reasons = [
("(x, y) coordinate consistency",
"#x-y-coordinate-consistency"),
("Functions parameterized by arguments, not by name",
"#functions-should-be-parameterized-by-arguments-not-by-name")]
def get_tool_docs(tools, chunks):
for tool in tools:
mod = imp.load_module(tool, open(tool, "r"), tool, ("", "", imp.PY_SOURCE))
chunks[tool + "_docs"] = mod.__doc__
def get_files_recursively(root, extensions):
for dirpath, dirnames, filenames in os.walk(root):
if not "build" in dirpath and not "dist" in dirpath:
for filename in filenames:
root, ext = os.path.splitext(filename)
if ext in extensions:
yield open(os.path.join(dirpath, filename), "r")
def find_all_matches(regex, file):
for match in regex.findall(file.read()):
yield match
def find_comments(file):
lines = file.readlines()
for lineno, line in enumerate(lines):
if "GAMERA_CPP_DEPRECATED" in line and not line.startswith("#"):
comment = []
for i in xrange(lineno, -1, -1):
if i < 0 or i >= len(lines):
raise RuntimeError("Couldn't find comment.")
find = lines[i].find(r"*/")
if find != -1:
comment.insert(0, lines[i][:find].strip())
break
for j in xrange(i - 1, -1, -1):
if j < 0 or j >= len(lines):
raise RuntimeError("Couldn't find comment.")
find = lines[j].find(r"/*")
if find != -1:
comment.insert(0, lines[j][find+2:].strip())
break
comment.insert(0, lines[j].strip())
comment = "\n".join(comment)
yield comment
def format_deprecations(deprecations):
table_line = "+" + ("-" * 80) + "+" + ("-" * 80) + "+\n"
table_header = "+" + ("=" * 80) + "+" + ("=" * 80) + "+\n"
output = cStringIO.StringIO()
output.write(table_line)
output.write("|%-80s|%-80s|\n" % ("Deprecated function", "Notes"))
output.write(table_header)
for deprecation in deprecations:
parts = deprecation.split("\n\n")
if len(parts) != 3:
continue
function = parts[0].strip()
reason = parts[1][len("Reason: "):].strip()
for i, r in enumerate(reasons):
if reason.startswith(r[0]):
function = function + " [%d_]" % (i + 1)
break
suggestion = "\n\n".join(parts[2:]).strip()
function = textwrap.wrap(function)
suggestion = textwrap.wrap(suggestion)
lines = max(len(function), len(suggestion))
for x in function, suggestion:
if len(x) < lines:
for i in range(lines - len(x)):
x.append("")
for a, b in zip(function, suggestion):
output.write("|%-80s|%-80s|\n" % (a, b))
output.write(table_line)
output.write("\n")
for i, r in enumerate(reasons):
output.write(".. _%d: %s\n" % (i + 1, r[1]))
output.write("\n")
return output.getvalue()
def get_cpp_deprecations():
deprecations = []
for fd in get_files_recursively("..", [".hpp", ".cpp"]):
for match in find_comments(fd):
deprecations.append(match)
deprecations.sort(lambda x, y: cmp(x.lower(), y.lower()))
print "%d C++ deprecations" % len(deprecations)
return deprecations
cpp_python_deprecation_regex = re.compile(
"send_deprecation_warning\((?P<x>.*?),\s*\"[^\"]+\",\s*__LINE__\)", re.DOTALL)
python_deprecation_regex = re.compile(
"warn_deprecated\((?P<x>(?:(?:(?:\"\"\".*?\"\"\")|(?:'''.*?''')|(?:\".*?\")|(?:\'.*?\'))\s*)+)\)", re.DOTALL)
python_deprecation_regex2 = re.compile("\.\.\s+warning::\s+(?P<x>.*?)\"\"\"", re.DOTALL)
def get_python_deprecations():
deprecations = []
for data in get_files_recursively("..", [".hpp", ".cpp"]):
for match in find_all_matches(cpp_python_deprecation_regex, data):
try:
deprecations.append(eval("(%s)" % match))
except SyntaxError:
pass
for data in get_files_recursively("..", [".py"]):
for match in find_all_matches(python_deprecation_regex, data):
try:
deprecations.append(eval(match))
except SyntaxError:
pass
for data in get_files_recursively("..", [".py"]):
for match in find_all_matches(python_deprecation_regex2, data):
deprecations.append(match)
deprecations.sort(lambda x, y: cmp(x.lower(), y.lower()))
print "%d Python deprecations" % len(deprecations)
return deprecations
def main():
chunks = {}
get_tool_docs("gamera_deprecation_filter replace_get_set".split(),
chunks)
chunks["cpp_deprecations"] = format_deprecations(get_cpp_deprecations())
chunks["python_deprecations"] = format_deprecations(get_python_deprecations())
output = open("../doc/src/migration_guide.txt", "w")
output.write(source % chunks)
output.close()
if __name__ == "__main__":
main()
|