20 Commits dc2d45f42f ... a31c505bd8

Author SHA1 Message Date
  Fabian Peter Hammerle a31c505bd8 fix type hints; add mypy to pipeline 3 years ago
  Fabian Peter Hammerle 9880c8d08b version.py: convert variable type hint to restore python3.5 compatibility 3 years ago
  Fabian Peter Hammerle ce1a4dad24 fix pylint C0321/multiple-statements in version.py 3 years ago
  Fabian Peter Hammerle bcd6c82c54 convert variable type hints to restore compatibility with python3.5 3 years ago
  Fabian Peter Hammerle 61ce708eea pipeline: run mypy 3 years ago
  Fabian Peter Hammerle fcb6f37239 fix & add type hints to fix remaining mypy errors 3 years ago
  Fabian Peter Hammerle 5a8812ec11 PolygonalChain: fix type hints 3 years ago
  Fabian Peter Hammerle 8b6cc31feb PolygonalCircuit.__eq__: fix argument type hint 3 years ago
  Fabian Peter Hammerle 91af7a603a PolygonalCircuit & subclasses: fix constructor's argument type hint 3 years ago
  Fabian Peter Hammerle 0a4041e1d7 fix mypy error at `__version__ = None` 3 years ago
  Fabian Peter Hammerle 4b90cba86e dev env: added mypy, removed autopep8 3 years ago
  Fabian Peter Hammerle cb896f82ac black code format 3 years ago
  Fabian Peter Hammerle 23a861edda release v1.1.1 3 years ago
  Fabian Peter Hammerle 2ae0898800 added changelog, add pylint-import-requirements to pipeline, upgrade dev env, fix linter warnings 3 years ago
  Fabian Peter Hammerle e23410b69d pipeline: added pylint-import-requirements 3 years ago
  Fabian Peter Hammerle eb007edcca dev env: fix ineffective numpy version constraint 3 years ago
  Fabian Peter Hammerle 510242b214 added changelog 3 years ago
  Fabian Peter Hammerle c9336bd4b8 setlocale: re-raise original exc when locale unsupported (pylint W0707/raise-missing-from) 3 years ago
  Fabian Peter Hammerle 573a63b424 dev env: downgrade numpy to restore compatibility with python3.5 3 years ago
  Fabian Peter Hammerle e3b7f39eea upgrade dev env 3 years ago

+ 32 - 0
.github/workflows/python.yml

@@ -0,0 +1,32 @@
+# sync with https://github.com/fphammerle/ical2vdir/blob/master/.github/workflows/python.yml
+
+# https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions
+
+# shown in badge
+# https://help.github.com/en/actions/automating-your-workflow-with-github-actions/configuring-a-workflow#adding-a-workflow-status-badge-to-your-repository
+name: tests
+
+on:
+  push:
+  pull_request:
+  schedule:
+  - cron: '0 20 * * 5'
+
+jobs:
+  code-format:
+    runs-on: ubuntu-18.04
+    strategy:
+      matrix:
+        python-version:
+        - 3.8
+    steps:
+    - uses: actions/checkout@v1
+    - uses: actions/setup-python@v1
+      with:
+        python-version: ${{ matrix.python-version }}
+    - run: pip install --upgrade pipenv==2020.8.13
+    - run: pipenv install --python "$PYTHON_VERSION" --deploy --dev
+      env:
+        PYTHON_VERSION: ${{ matrix.python-version }}
+    - run: pipenv graph
+    - run: pipenv run black --check .

+ 1 - 0
.gitignore

@@ -1,5 +1,6 @@
 .coverage
 .ipynb_checkpoints/
+.mypy_cache/
 build/
 dist/
 tags

+ 3 - 1
.travis.yml

@@ -14,8 +14,10 @@ install:
 - pipenv graph
 
 script:
-- pipenv run pylint freesurfer_surface tests/*
 - pipenv run pytest --cov=freesurfer_surface --cov-report=term-missing --cov-fail-under=100
+- pipenv run pylint --load-plugins=pylint_import_requirements freesurfer_surface
+- pipenv run pylint tests/*
+- pipenv run mypy freesurfer_surface tests
 
 after_success:
 - pip install coveralls

+ 71 - 0
CHANGELOG.md

@@ -0,0 +1,71 @@
+# Changelog
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [Unreleased]
+### Fixed
+- type hints
+
+## [1.1.1] - 2020-10-18
+### Fixed
+- fix `ModuleNotFoundError` on `import freesurfer_surface.version`
+  for developers not installing package
+  (https://github.com/fphammerle/freesurfer-surface/issues/22)
+- `setlocale`: re-raise original exception
+  when the selected locale is not supported
+
+## [1.1.0] - 2019-05-23
+### Added
+* added static method `Surface.unite(surfaces)`
+* added command-line entry point / script `unite-freesurfer-surfaces`
+
+## [1.0.1] - 2019-05-23
+### Fixed
+- `Surface.find_borders()` failed on self-crossing borders
+
+## [1.0.0] - 2019-05-19
+## Added
+- method `Surface.find_borders()`
+- method `Surface.remove_unused_vertices()`
+- method `Surface.select_vertices(vertex_indices)`
+- method `Vertex.distance_mm(other_vertices)`
+- class `LineSegment`
+- class `PolygonalCircuit`
+
+## Changed
+* property `Triangle.vertex_indices` is now read-only
+
+## [0.2.2] - 2019-05-19
+### Fixed
+- fixed comparison & hashing of `Triangle` / `_PolygonalCircuit`
+  (`_PolygonalCircuit._normalize` now considers order of indices)
+
+## [0.2.1] - 2019-05-16
+### Fixed
+- fix formatting of project description on pypi
+
+## [0.2.0] - 2019-05-15
+### Added
+- added `PolygonalChain.adjacent_vertex_indices()`
+- `Surface.add_rectangle`: accept 3 or 4 vertex indices (previously only 3)
+
+### Changed
+- dervice `Vertex` from `numpy.ndarray` (previously `collections.namedtuple`)
+
+### Fixed
+* `Surface._find_label_border_segments`: failing on incompletely labelled set of vertices
+
+## [0.1.0] - 2019-05-12
+tagger Fabian Peter Hammerle <fabian@hammerle.me> 1557674236 +0200
+
+[Unreleased]: https://github.com/fphammerle/freesurfer-stats/compare/1.1.1...HEAD
+[1.1.1]: https://github.com/fphammerle/freesurfer-stats/compare/1.1.0...1.1.1
+[1.1.0]: https://github.com/fphammerle/freesurfer-stats/compare/1.0.1...1.1.0
+[1.0.1]: https://github.com/fphammerle/freesurfer-stats/compare/1.0.0...1.0.1
+[1.0.0]: https://github.com/fphammerle/freesurfer-stats/compare/0.2.2...1.0.0
+[0.2.2]: https://github.com/fphammerle/freesurfer-stats/compare/0.2.1...0.2.2
+[0.2.1]: https://github.com/fphammerle/freesurfer-stats/compare/0.2.0...0.2.1
+[0.2.0]: https://github.com/fphammerle/freesurfer-stats/compare/0.1.0...0.2.0
+[0.1.0]: https://github.com/fphammerle/freesurfer-stats/releases/tag/0.1.0

+ 19 - 7
Pipfile

@@ -6,16 +6,28 @@ name = "pypi"
 [packages]
 freesurfer-surface = {editable = true, path = "."}
 
+# python3.5 compatibility
+# https://github.com/numpy/numpy/commit/fbcb58ca2c17217f5dc85cb669ea651b7799b60c#diff-60f61ab7a8d1910d86d9fda2261620314edcae5894d5aaa236b821c7256badd7R30
+numpy = "<1.19"
+
 [dev-packages]
-pylint = ">=2.3.0,<3"
-pytest = "<5"
-pytest-cov = "<3,>=2"
-"autopep8" = "<2"
+# black requires python>=3.6
+# https://github.com/psf/black/commit/e74117f172e29e8a980e2c9de929ad50d3769150#diff-2eeaed663bd0d25b7e608891384b7298R51
+black = {version = "==20.8b1", markers = "python_version >= '3.6'"}
+mypy = "*"
+pylint = "*"
+pylint-import-requirements = "*"
+pytest = "*"
+pytest-cov = "*"
+
+# python3.5 compatibility
 isort = "<5"
-# >=1.4.0 python3.8
-typed-ast = ">=1.4.0"
-# <2 python3.5
+# workaround https://github.com/pytest-dev/pytest/issues/3953
+pathlib2 = {version = "*", markers="python_version < '3.6'"}
+# https://github.com/jaraco/zipp/commit/05a3c52b4d41690e0471a2e283cffb500dc0329a
 zipp = "<2"
 
 [requires]
 python_version = "3"
+
+# Pipfile syntax: https://github.com/pypa/pipfile#pipfile

+ 207 - 114
Pipfile.lock

@@ -1,7 +1,7 @@
 {
     "_meta": {
         "hash": {
-            "sha256": "803d32a037feb7833276e483dc360026bd99e45f1fafaf9dc5ccde67f1469424"
+            "sha256": "d467a3af09912d82994939bcd0595d241bdb3c98dc3602b721498f09545fb373"
         },
         "pipfile-spec": 6,
         "requires": {
@@ -22,103 +22,121 @@
         },
         "numpy": {
             "hashes": [
-                "sha256:1786a08236f2c92ae0e70423c45e1e62788ed33028f94ca99c4df03f5be6b3c6",
-                "sha256:17aa7a81fe7599a10f2b7d95856dc5cf84a4eefa45bc96123cbbc3ebc568994e",
-                "sha256:20b26aaa5b3da029942cdcce719b363dbe58696ad182aff0e5dcb1687ec946dc",
-                "sha256:2d75908ab3ced4223ccba595b48e538afa5ecc37405923d1fea6906d7c3a50bc",
-                "sha256:39d2c685af15d3ce682c99ce5925cc66efc824652e10990d2462dfe9b8918c6a",
-                "sha256:56bc8ded6fcd9adea90f65377438f9fea8c05fcf7c5ba766bef258d0da1554aa",
-                "sha256:590355aeade1a2eaba17617c19edccb7db8d78760175256e3cf94590a1a964f3",
-                "sha256:70a840a26f4e61defa7bdf811d7498a284ced303dfbc35acb7be12a39b2aa121",
-                "sha256:77c3bfe65d8560487052ad55c6998a04b654c2fbc36d546aef2b2e511e760971",
-                "sha256:9537eecf179f566fd1c160a2e912ca0b8e02d773af0a7a1120ad4f7507cd0d26",
-                "sha256:9acdf933c1fd263c513a2df3dceecea6f3ff4419d80bf238510976bf9bcb26cd",
-                "sha256:ae0975f42ab1f28364dcda3dde3cf6c1ddab3e1d4b2909da0cb0191fa9ca0480",
-                "sha256:b3af02ecc999c8003e538e60c89a2b37646b39b688d4e44d7373e11c2debabec",
-                "sha256:b6ff59cee96b454516e47e7721098e6ceebef435e3e21ac2d6c3b8b02628eb77",
-                "sha256:b765ed3930b92812aa698a455847141869ef755a87e099fddd4ccf9d81fffb57",
-                "sha256:c98c5ffd7d41611407a1103ae11c8b634ad6a43606eca3e2a5a269e5d6e8eb07",
-                "sha256:cf7eb6b1025d3e169989416b1adcd676624c2dbed9e3bcb7137f51bfc8cc2572",
-                "sha256:d92350c22b150c1cae7ebb0ee8b5670cc84848f6359cf6b5d8f86617098a9b73",
-                "sha256:e422c3152921cece8b6a2fb6b0b4d73b6579bd20ae075e7d15143e711f3ca2ca",
-                "sha256:e840f552a509e3380b0f0ec977e8124d0dc34dc0e68289ca28f4d7c1d0d79474",
-                "sha256:f3d0a94ad151870978fb93538e95411c83899c9dc63e6fb65542f769568ecfa5"
+                "sha256:0172304e7d8d40e9e49553901903dc5f5a49a703363ed756796f5808a06fc233",
+                "sha256:34e96e9dae65c4839bd80012023aadd6ee2ccb73ce7fdf3074c62f301e63120b",
+                "sha256:3676abe3d621fc467c4c1469ee11e395c82b2d6b5463a9454e37fe9da07cd0d7",
+                "sha256:3dd6823d3e04b5f223e3e265b4a1eae15f104f4366edd409e5a5e413a98f911f",
+                "sha256:4064f53d4cce69e9ac613256dc2162e56f20a4e2d2086b1956dd2fcf77b7fac5",
+                "sha256:4674f7d27a6c1c52a4d1aa5f0881f1eff840d2206989bae6acb1c7668c02ebfb",
+                "sha256:7d42ab8cedd175b5ebcb39b5208b25ba104842489ed59fbb29356f671ac93583",
+                "sha256:965df25449305092b23d5145b9bdaeb0149b6e41a77a7d728b1644b3c99277c1",
+                "sha256:9c9d6531bc1886454f44aa8f809268bc481295cf9740827254f53c30104f074a",
+                "sha256:a78e438db8ec26d5d9d0e584b27ef25c7afa5a182d1bf4d05e313d2d6d515271",
+                "sha256:a7acefddf994af1aeba05bbbafe4ba983a187079f125146dc5859e6d817df824",
+                "sha256:a87f59508c2b7ceb8631c20630118cc546f1f815e034193dc72390db038a5cb3",
+                "sha256:ac792b385d81151bae2a5a8adb2b88261ceb4976dbfaaad9ce3a200e036753dc",
+                "sha256:b03b2c0badeb606d1232e5f78852c102c0a7989d3a534b3129e7856a52f3d161",
+                "sha256:b39321f1a74d1f9183bf1638a745b4fd6fe80efbb1f6b32b932a588b4bc7695f",
+                "sha256:cae14a01a159b1ed91a324722d746523ec757357260c6804d11d6147a9e53e3f",
+                "sha256:cd49930af1d1e49a812d987c2620ee63965b619257bd76eaaa95870ca08837cf",
+                "sha256:e15b382603c58f24265c9c931c9a45eebf44fe2e6b4eaedbb0d025ab3255228b",
+                "sha256:e91d31b34fc7c2c8f756b4e902f901f856ae53a93399368d9a0dc7be17ed2ca0",
+                "sha256:ef627986941b5edd1ed74ba89ca43196ed197f1a206a3f18cc9faf2fb84fd675",
+                "sha256:f718a7949d1c4f622ff548c572e0c03440b49b9531ff00e4ed5738b459f011e8"
             ],
-            "version": "==1.18.1"
+            "index": "pypi",
+            "version": "==1.18.5"
         }
     },
     "develop": {
-        "astroid": {
+        "appdirs": {
             "hashes": [
-                "sha256:71ea07f44df9568a75d0f354c49143a4575d90645e9fead6dfb52c26a85ed13a",
-                "sha256:840947ebfa8b58f318d42301cf8c0a20fd794a33b61cc4638e28e9e61ba32f42"
+                "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41",
+                "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"
             ],
-            "version": "==2.3.3"
+            "version": "==1.4.4"
         },
-        "atomicwrites": {
+        "astroid": {
             "hashes": [
-                "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4",
-                "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"
+                "sha256:2f4078c2a41bf377eea06d71c9d2ba4eb8f6b1af2135bec27bbbb7d8f12bb703",
+                "sha256:bc58d83eb610252fd8de6363e39d4f1d0619c894b0ed24603b881c02e64c7386"
             ],
-            "version": "==1.3.0"
+            "version": "==2.4.2"
         },
         "attrs": {
             "hashes": [
-                "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c",
-                "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"
+                "sha256:26b54ddbbb9ee1d34d5d3668dd37d6cf74990ab23c828c2888dccdceee395594",
+                "sha256:fce7fc47dfc976152e82d53ff92fa0407700c21acd20886a13777a0d20e655dc"
             ],
-            "version": "==19.3.0"
+            "version": "==20.2.0"
         },
-        "autopep8": {
+        "black": {
             "hashes": [
-                "sha256:0f592a0447acea0c2b0a9602be1e4e3d86db52badd2e3c84f0193bfd89fd3a43"
+                "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"
             ],
             "index": "pypi",
-            "version": "==1.5"
+            "markers": "python_version >= '3.6'",
+            "version": "==20.8b1"
+        },
+        "click": {
+            "hashes": [
+                "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a",
+                "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"
+            ],
+            "version": "==7.1.2"
         },
         "coverage": {
             "hashes": [
-                "sha256:15cf13a6896048d6d947bf7d222f36e4809ab926894beb748fc9caa14605d9c3",
-                "sha256:1daa3eceed220f9fdb80d5ff950dd95112cd27f70d004c7918ca6dfc6c47054c",
-                "sha256:1e44a022500d944d42f94df76727ba3fc0a5c0b672c358b61067abb88caee7a0",
-                "sha256:25dbf1110d70bab68a74b4b9d74f30e99b177cde3388e07cc7272f2168bd1477",
-                "sha256:3230d1003eec018ad4a472d254991e34241e0bbd513e97a29727c7c2f637bd2a",
-                "sha256:3dbb72eaeea5763676a1a1efd9b427a048c97c39ed92e13336e726117d0b72bf",
-                "sha256:5012d3b8d5a500834783689a5d2292fe06ec75dc86ee1ccdad04b6f5bf231691",
-                "sha256:51bc7710b13a2ae0c726f69756cf7ffd4362f4ac36546e243136187cfcc8aa73",
-                "sha256:527b4f316e6bf7755082a783726da20671a0cc388b786a64417780b90565b987",
-                "sha256:722e4557c8039aad9592c6a4213db75da08c2cd9945320220634f637251c3894",
-                "sha256:76e2057e8ffba5472fd28a3a010431fd9e928885ff480cb278877c6e9943cc2e",
-                "sha256:77afca04240c40450c331fa796b3eab6f1e15c5ecf8bf2b8bee9706cd5452fef",
-                "sha256:7afad9835e7a651d3551eab18cbc0fdb888f0a6136169fbef0662d9cdc9987cf",
-                "sha256:9bea19ac2f08672636350f203db89382121c9c2ade85d945953ef3c8cf9d2a68",
-                "sha256:a8b8ac7876bc3598e43e2603f772d2353d9931709345ad6c1149009fd1bc81b8",
-                "sha256:b0840b45187699affd4c6588286d429cd79a99d509fe3de0f209594669bb0954",
-                "sha256:b26aaf69713e5674efbde4d728fb7124e429c9466aeaf5f4a7e9e699b12c9fe2",
-                "sha256:b63dd43f455ba878e5e9f80ba4f748c0a2156dde6e0e6e690310e24d6e8caf40",
-                "sha256:be18f4ae5a9e46edae3f329de2191747966a34a3d93046dbdf897319923923bc",
-                "sha256:c312e57847db2526bc92b9bfa78266bfbaabac3fdcd751df4d062cd4c23e46dc",
-                "sha256:c60097190fe9dc2b329a0eb03393e2e0829156a589bd732e70794c0dd804258e",
-                "sha256:c62a2143e1313944bf4a5ab34fd3b4be15367a02e9478b0ce800cb510e3bbb9d",
-                "sha256:cc1109f54a14d940b8512ee9f1c3975c181bbb200306c6d8b87d93376538782f",
-                "sha256:cd60f507c125ac0ad83f05803063bed27e50fa903b9c2cfee3f8a6867ca600fc",
-                "sha256:d513cc3db248e566e07a0da99c230aca3556d9b09ed02f420664e2da97eac301",
-                "sha256:d649dc0bcace6fcdb446ae02b98798a856593b19b637c1b9af8edadf2b150bea",
-                "sha256:d7008a6796095a79544f4da1ee49418901961c97ca9e9d44904205ff7d6aa8cb",
-                "sha256:da93027835164b8223e8e5af2cf902a4c80ed93cb0909417234f4a9df3bcd9af",
-                "sha256:e69215621707119c6baf99bda014a45b999d37602cb7043d943c76a59b05bf52",
-                "sha256:ea9525e0fef2de9208250d6c5aeeee0138921057cd67fcef90fbed49c4d62d37",
-                "sha256:fca1669d464f0c9831fd10be2eef6b86f5ebd76c724d1e0706ebdff86bb4adf0"
+                "sha256:0203acd33d2298e19b57451ebb0bed0ab0c602e5cf5a818591b4918b1f97d516",
+                "sha256:0f313707cdecd5cd3e217fc68c78a960b616604b559e9ea60cc16795c4304259",
+                "sha256:1c6703094c81fa55b816f5ae542c6ffc625fec769f22b053adb42ad712d086c9",
+                "sha256:1d44bb3a652fed01f1f2c10d5477956116e9b391320c94d36c6bf13b088a1097",
+                "sha256:280baa8ec489c4f542f8940f9c4c2181f0306a8ee1a54eceba071a449fb870a0",
+                "sha256:29a6272fec10623fcbe158fdf9abc7a5fa032048ac1d8631f14b50fbfc10d17f",
+                "sha256:2b31f46bf7b31e6aa690d4c7a3d51bb262438c6dcb0d528adde446531d0d3bb7",
+                "sha256:2d43af2be93ffbad25dd959899b5b809618a496926146ce98ee0b23683f8c51c",
+                "sha256:381ead10b9b9af5f64646cd27107fb27b614ee7040bb1226f9c07ba96625cbb5",
+                "sha256:47a11bdbd8ada9b7ee628596f9d97fbd3851bd9999d398e9436bd67376dbece7",
+                "sha256:4d6a42744139a7fa5b46a264874a781e8694bb32f1d76d8137b68138686f1729",
+                "sha256:50691e744714856f03a86df3e2bff847c2acede4c191f9a1da38f088df342978",
+                "sha256:530cc8aaf11cc2ac7430f3614b04645662ef20c348dce4167c22d99bec3480e9",
+                "sha256:582ddfbe712025448206a5bc45855d16c2e491c2dd102ee9a2841418ac1c629f",
+                "sha256:63808c30b41f3bbf65e29f7280bf793c79f54fb807057de7e5238ffc7cc4d7b9",
+                "sha256:71b69bd716698fa62cd97137d6f2fdf49f534decb23a2c6fc80813e8b7be6822",
+                "sha256:7858847f2d84bf6e64c7f66498e851c54de8ea06a6f96a32a1d192d846734418",
+                "sha256:78e93cc3571fd928a39c0b26767c986188a4118edc67bc0695bc7a284da22e82",
+                "sha256:7f43286f13d91a34fadf61ae252a51a130223c52bfefb50310d5b2deb062cf0f",
+                "sha256:86e9f8cd4b0cdd57b4ae71a9c186717daa4c5a99f3238a8723f416256e0b064d",
+                "sha256:8f264ba2701b8c9f815b272ad568d555ef98dfe1576802ab3149c3629a9f2221",
+                "sha256:9342dd70a1e151684727c9c91ea003b2fb33523bf19385d4554f7897ca0141d4",
+                "sha256:9361de40701666b034c59ad9e317bae95c973b9ff92513dd0eced11c6adf2e21",
+                "sha256:9669179786254a2e7e57f0ecf224e978471491d660aaca833f845b72a2df3709",
+                "sha256:aac1ba0a253e17889550ddb1b60a2063f7474155465577caa2a3b131224cfd54",
+                "sha256:aef72eae10b5e3116bac6957de1df4d75909fc76d1499a53fb6387434b6bcd8d",
+                "sha256:bd3166bb3b111e76a4f8e2980fa1addf2920a4ca9b2b8ca36a3bc3dedc618270",
+                "sha256:c1b78fb9700fc961f53386ad2fd86d87091e06ede5d118b8a50dea285a071c24",
+                "sha256:c3888a051226e676e383de03bf49eb633cd39fc829516e5334e69b8d81aae751",
+                "sha256:c5f17ad25d2c1286436761b462e22b5020d83316f8e8fcb5deb2b3151f8f1d3a",
+                "sha256:c851b35fc078389bc16b915a0a7c1d5923e12e2c5aeec58c52f4aa8085ac8237",
+                "sha256:cb7df71de0af56000115eafd000b867d1261f786b5eebd88a0ca6360cccfaca7",
+                "sha256:cedb2f9e1f990918ea061f28a0f0077a07702e3819602d3507e2ff98c8d20636",
+                "sha256:e8caf961e1b1a945db76f1b5fa9c91498d15f545ac0ababbe575cfab185d3bd8"
             ],
-            "version": "==5.0.3"
+            "version": "==5.3"
         },
         "importlib-metadata": {
             "hashes": [
-                "sha256:06f5b3a99029c7134207dd882428a66992a9de2bef7c2b699b5641f9886c3302",
-                "sha256:b97607a1a18a5100839aec1dc26a1ea17ee0d93b20b0f008d80a5a050afb200b"
+                "sha256:77a540690e24b0305878c37ffd421785a6f7e53c8b5720d211b211de8d0e95da",
+                "sha256:cefa1a2f919b866c5beb7c9f7b0ebb4061f30a8a9bf16d609b000e2dfaceb9c3"
             ],
-            "markers": "python_version < '3.8'",
-            "version": "==1.5.0"
+            "version": "==2.0.0"
+        },
+        "iniconfig": {
+            "hashes": [
+                "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3",
+                "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"
+            ],
+            "version": "==1.1.1"
         },
         "isort": {
             "hashes": [
@@ -161,20 +179,55 @@
             ],
             "version": "==0.6.1"
         },
-        "more-itertools": {
+        "mypy": {
             "hashes": [
-                "sha256:5dd8bcf33e5f9513ffa06d5ad33d78f31e1931ac9a18f33d37e77a180d393a7c",
-                "sha256:b1ddb932186d8a6ac451e1d95844b382f55e12686d51ca0c68b6f61f2ab7a507"
+                "sha256:0a0d102247c16ce93c97066443d11e2d36e6cc2a32d8ccc1f705268970479324",
+                "sha256:0d34d6b122597d48a36d6c59e35341f410d4abfa771d96d04ae2c468dd201abc",
+                "sha256:2170492030f6faa537647d29945786d297e4862765f0b4ac5930ff62e300d802",
+                "sha256:2842d4fbd1b12ab422346376aad03ff5d0805b706102e475e962370f874a5122",
+                "sha256:2b21ba45ad9ef2e2eb88ce4aeadd0112d0f5026418324176fd494a6824b74975",
+                "sha256:72060bf64f290fb629bd4a67c707a66fd88ca26e413a91384b18db3876e57ed7",
+                "sha256:af4e9ff1834e565f1baa74ccf7ae2564ae38c8df2a85b057af1dbbc958eb6666",
+                "sha256:bd03b3cf666bff8d710d633d1c56ab7facbdc204d567715cb3b9f85c6e94f669",
+                "sha256:c614194e01c85bb2e551c421397e49afb2872c88b5830e3554f0519f9fb1c178",
+                "sha256:cf4e7bf7f1214826cf7333627cb2547c0db7e3078723227820d0a2490f117a01",
+                "sha256:da56dedcd7cd502ccd3c5dddc656cb36113dd793ad466e894574125945653cea",
+                "sha256:e86bdace26c5fe9cf8cb735e7cedfe7850ad92b327ac5d797c656717d2ca66de",
+                "sha256:e97e9c13d67fbe524be17e4d8025d51a7dca38f90de2e462243ab8ed8a9178d1",
+                "sha256:eea260feb1830a627fb526d22fbb426b750d9f5a47b624e8d5e7e004359b219c"
             ],
-            "markers": "python_version > '2.7'",
-            "version": "==8.2.0"
+            "index": "pypi",
+            "version": "==0.790"
+        },
+        "mypy-extensions": {
+            "hashes": [
+                "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d",
+                "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"
+            ],
+            "version": "==0.4.3"
         },
         "packaging": {
             "hashes": [
-                "sha256:170748228214b70b672c581a3dd610ee51f733018650740e98c7df862a583f73",
-                "sha256:e665345f9eef0c621aa0bf2f8d78cf6d21904eef16a93f020240b704a57f1334"
+                "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8",
+                "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"
+            ],
+            "version": "==20.4"
+        },
+        "pathlib2": {
+            "hashes": [
+                "sha256:0ec8205a157c80d7acc301c0b18fbd5d44fe655968f5d947b6ecef5290fc35db",
+                "sha256:6cd9a47b597b37cc57de1c05e56fb1a1c9cc9fab04fe78c29acd090418529868"
+            ],
+            "index": "pypi",
+            "markers": "python_version < '3.6'",
+            "version": "==2.3.5"
+        },
+        "pathspec": {
+            "hashes": [
+                "sha256:7d91249d21749788d07a2d0f94147accd8f845507400749ea19c1ec9054a12b0",
+                "sha256:da45173eb3a6f2a5a487efba21f050af2b41948be6ab52b6a1e3ff22bb8b7061"
             ],
-            "version": "==20.1"
+            "version": "==0.8.0"
         },
         "pluggy": {
             "hashes": [
@@ -185,55 +238,95 @@
         },
         "py": {
             "hashes": [
-                "sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa",
-                "sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0"
+                "sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2",
+                "sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342"
             ],
-            "version": "==1.8.1"
+            "version": "==1.9.0"
         },
-        "pycodestyle": {
+        "pylint": {
             "hashes": [
-                "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56",
-                "sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c"
+                "sha256:bb4a908c9dadbc3aac18860550e870f58e1a02c9f2c204fdf5693d73be061210",
+                "sha256:bfe68f020f8a0fece830a22dd4d5dddb4ecc6137db04face4c3420a46a52239f"
             ],
-            "version": "==2.5.0"
+            "index": "pypi",
+            "version": "==2.6.0"
         },
-        "pylint": {
+        "pylint-import-requirements": {
             "hashes": [
-                "sha256:3db5468ad013380e987410a8d6956226963aed94ecb5f9d3a28acca6d9ac36cd",
-                "sha256:886e6afc935ea2590b462664b161ca9a5e40168ea99e5300935f6591ad467df4"
+                "sha256:39d0993baa0991c2c6258d310aacd5f69c850ba59cb3d2ae8aa69886886b7135",
+                "sha256:bc138263bf1f79d8d1bf3d3f6786248024a71b4202cd19a189f1346f3e5b20a7"
             ],
             "index": "pypi",
-            "version": "==2.4.4"
+            "version": "==2.0.5"
         },
         "pyparsing": {
             "hashes": [
-                "sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f",
-                "sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec"
+                "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
+                "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
             ],
-            "version": "==2.4.6"
+            "version": "==2.4.7"
         },
         "pytest": {
             "hashes": [
-                "sha256:19e8f75eac01dd3f211edd465b39efbcbdc8fc5f7866d7dd49fedb30d8adf339",
-                "sha256:c77a5f30a90e0ce24db9eaa14ddfd38d4afb5ea159309bdd2dae55b931bc9324"
+                "sha256:7a8190790c17d79a11f847fba0b004ee9a8122582ebff4729a082c109e81a4c9",
+                "sha256:8f593023c1a0f916110285b6efd7f99db07d59546e3d8c36fc60e2ab05d3be92"
             ],
             "index": "pypi",
-            "version": "==4.6.9"
+            "version": "==6.1.1"
         },
         "pytest-cov": {
             "hashes": [
-                "sha256:cc6742d8bac45070217169f5f72ceee1e0e55b0221f54bcf24845972d3a47f2b",
-                "sha256:cdbdef4f870408ebdbfeb44e63e07eb18bb4619fae852f6e760645fa36172626"
+                "sha256:45ec2d5182f89a81fc3eb29e3d1ed3113b9e9a873bcddb2a71faaab066110191",
+                "sha256:47bd0ce14056fdd79f93e1713f88fad7bdcc583dcd7783da86ef2f085a0bb88e"
             ],
             "index": "pypi",
-            "version": "==2.8.1"
+            "version": "==2.10.1"
+        },
+        "regex": {
+            "hashes": [
+                "sha256:02686a2f0b1a4be0facdd0d3ad4dc6c23acaa0f38fb5470d892ae88584ba705c",
+                "sha256:137da580d1e6302484be3ef41d72cf5c3ad22a076070051b7449c0e13ab2c482",
+                "sha256:20cdd7e1736f4f61a5161aa30d05ac108ab8efc3133df5eb70fe1e6a23ea1ca6",
+                "sha256:25991861c6fef1e5fd0a01283cf5658c5e7f7aa644128e85243bc75304e91530",
+                "sha256:26b85672275d8c7a9d4ff93dbc4954f5146efdb2ecec89ad1de49439984dea14",
+                "sha256:2f60ba5c33f00ce9be29a140e6f812e39880df8ba9cb92ad333f0016dbc30306",
+                "sha256:3dd952f3f8dc01b72c0cf05b3631e05c50ac65ddd2afdf26551638e97502107b",
+                "sha256:578ac6379e65eb8e6a85299b306c966c852712c834dc7eef0ba78d07a828f67b",
+                "sha256:5d4a3221f37520bb337b64a0632716e61b26c8ae6aaffceeeb7ad69c009c404b",
+                "sha256:608d6c05452c0e6cc49d4d7407b4767963f19c4d2230fa70b7201732eedc84f2",
+                "sha256:65b6b018b07e9b3b6a05c2c3bb7710ed66132b4df41926c243887c4f1ff303d5",
+                "sha256:698f8a5a2815e1663d9895830a063098ae2f8f2655ae4fdc5dfa2b1f52b90087",
+                "sha256:6c72adb85adecd4522a488a751e465842cdd2a5606b65464b9168bf029a54272",
+                "sha256:6d4cdb6c20e752426b2e569128488c5046fb1b16b1beadaceea9815c36da0847",
+                "sha256:6e9f72e0ee49f7d7be395bfa29e9533f0507a882e1e6bf302c0a204c65b742bf",
+                "sha256:828618f3c3439c5e6ef8621e7c885ca561bbaaba90ddbb6a7dfd9e1ec8341103",
+                "sha256:85b733a1ef2b2e7001aff0e204a842f50ad699c061856a214e48cfb16ace7d0c",
+                "sha256:8958befc139ac4e3f16d44ec386c490ea2121ed8322f4956f83dd9cad8e9b922",
+                "sha256:a51e51eecdac39a50ede4aeed86dbef4776e3b73347d31d6ad0bc9648ba36049",
+                "sha256:aeac7c9397480450016bc4a840eefbfa8ca68afc1e90648aa6efbfe699e5d3bb",
+                "sha256:aef23aed9d4017cc74d37f703d57ce254efb4c8a6a01905f40f539220348abf9",
+                "sha256:af1f5e997dd1ee71fb6eb4a0fb6921bf7a778f4b62f1f7ef0d7445ecce9155d6",
+                "sha256:b5eeaf4b5ef38fab225429478caf71f44d4a0b44d39a1aa4d4422cda23a9821b",
+                "sha256:d25f5cca0f3af6d425c9496953445bf5b288bb5b71afc2b8308ad194b714c159",
+                "sha256:d81be22d5d462b96a2aa5c512f741255ba182995efb0114e5a946fe254148df1",
+                "sha256:e935a166a5f4c02afe3f7e4ce92ce5a786f75c6caa0c4ce09c922541d74b77e8",
+                "sha256:ef3a55b16c6450574734db92e0a3aca283290889934a23f7498eaf417e3af9f0"
+            ],
+            "version": "==2020.10.15"
         },
         "six": {
             "hashes": [
-                "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a",
-                "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"
+                "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
+                "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
             ],
-            "version": "==1.14.0"
+            "version": "==1.15.0"
+        },
+        "toml": {
+            "hashes": [
+                "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f",
+                "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88"
+            ],
+            "version": "==0.10.1"
         },
         "typed-ast": {
             "hashes": [
@@ -259,29 +352,29 @@
                 "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4",
                 "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"
             ],
-            "index": "pypi",
             "version": "==1.4.1"
         },
-        "wcwidth": {
+        "typing-extensions": {
             "hashes": [
-                "sha256:8fd29383f539be45b20bd4df0dc29c20ba48654a41e661925e612311e9f3c603",
-                "sha256:f28b3e8a6483e5d49e7f8949ac1a78314e740333ae305b4ba5defd3e74fb37a8"
+                "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918",
+                "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c",
+                "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"
             ],
-            "version": "==0.1.8"
+            "version": "==3.7.4.3"
         },
         "wrapt": {
             "hashes": [
-                "sha256:565a021fd19419476b9362b05eeaa094178de64f8361e44468f9e9d7843901e1"
+                "sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7"
             ],
-            "version": "==1.11.2"
+            "version": "==1.12.1"
         },
         "zipp": {
             "hashes": [
-                "sha256:15428d652e993b6ce86694c3cccf0d71aa7afdc6ef1807fa25a920e9444e0281",
-                "sha256:d9d2efe11d3a3fb9184da550d35bd1319dc8e30a63255927c82bb42fca1f4f7c"
+                "sha256:c70410551488251b0fee67b460fb9a536af8d6f9f008ad10ac51f615b6a521b1",
+                "sha256:e0d9e63797e483a30d27e09fffd308c59a700d365ec34e93cc100844168bf921"
             ],
             "index": "pypi",
-            "version": "==1.1.0"
+            "version": "==1.2.0"
         }
     }
 }

+ 2 - 0
README.rst

@@ -1,6 +1,8 @@
 freesurfer-surface
 ==================
 
+.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
+   :target: https://github.com/psf/black
 .. image:: https://travis-ci.org/fphammerle/freesurfer-surface.svg?branch=master
    :target: https://travis-ci.org/fphammerle/freesurfer-surface
 .. image:: https://coveralls.io/repos/github/fphammerle/freesurfer-surface/badge.svg?branch=master

+ 249 - 169
freesurfer_surface/__init__.py

@@ -65,18 +65,16 @@ def setlocale(temporary_locale):
     try:
         yield locale.setlocale(locale.LC_ALL, temporary_locale)
     except locale.Error as exc:
-        if str(exc) == 'unsupported locale setting':
-            raise UnsupportedLocaleSettingError(temporary_locale)
+        if str(exc) == "unsupported locale setting":
+            raise UnsupportedLocaleSettingError(temporary_locale) from exc
         raise exc  # pragma: no cover
     finally:
         locale.setlocale(locale.LC_ALL, primary_locale)
 
 
 class Vertex(numpy.ndarray):
-
     def __new__(cls, right: float, anterior: float, superior: float):
-        return numpy.array((right, anterior, superior),
-                           dtype=float).view(cls)
+        return numpy.array((right, anterior, superior), dtype=float).view(cls)
 
     @property
     def right(self) -> float:
@@ -91,32 +89,33 @@ class Vertex(numpy.ndarray):
         return self[2]
 
     @property
-    def __dict__(self) -> typing.Dict[str, float]:
-        return {'right': self.right,
-                'anterior': self.anterior,
-                'superior': self.superior}
+    def __dict__(self) -> typing.Dict[str, typing.Any]:  # type: ignore
+        # type hint: https://github.com/python/mypy/issues/6523#issuecomment-470733447
+        return {
+            "right": self.right,
+            "anterior": self.anterior,
+            "superior": self.superior,
+        }
 
     def __format_coords(self) -> str:
-        return ', '.join('{}={}'.format(name, getattr(self, name))
-                         for name in ['right', 'anterior', 'superior'])
+        return ", ".join(
+            "{}={}".format(name, getattr(self, name))
+            for name in ["right", "anterior", "superior"]
+        )
 
     def __repr__(self) -> str:
-        return '{}({})'.format(type(self).__name__, self.__format_coords())
+        return "{}({})".format(type(self).__name__, self.__format_coords())
 
-    def distance_mm(self, others: typing.Union['Vertex',
-                                               typing.Iterable['Vertex'],
-                                               numpy.ndarray],
-                    ) -> numpy.ndarray:
+    def distance_mm(
+        self, others: typing.Union["Vertex", typing.Iterable["Vertex"], numpy.ndarray]
+    ) -> numpy.ndarray:
         if isinstance(others, Vertex):
             others = others.reshape((1, 3))
         return numpy.linalg.norm(self - others, axis=1)
 
 
 class PolygonalCircuit:
-
-    _VERTEX_INDICES_TYPE = typing.Tuple[int]
-
-    def __init__(self, vertex_indices: _VERTEX_INDICES_TYPE):
+    def __init__(self, vertex_indices: typing.Iterable[int]):
         self._vertex_indices = tuple(vertex_indices)
         assert all(isinstance(idx, int) for idx in self._vertex_indices)
 
@@ -124,7 +123,7 @@ class PolygonalCircuit:
     def vertex_indices(self):
         return self._vertex_indices
 
-    def _normalize(self) -> 'PolygonalCircuit':
+    def _normalize(self) -> "PolygonalCircuit":
         vertex_indices = collections.deque(self.vertex_indices)
         vertex_indices.rotate(-numpy.argmin(self.vertex_indices))
         if len(vertex_indices) > 2 and vertex_indices[-1] < vertex_indices[1]:
@@ -132,45 +131,53 @@ class PolygonalCircuit:
             vertex_indices.rotate(1)
         return type(self)(vertex_indices)
 
-    def __eq__(self, other: 'PolygonalCircuit') -> bool:
+    def __eq__(self, other: object) -> bool:
         # pylint: disable=protected-access
-        return self._normalize().vertex_indices == other._normalize().vertex_indices
+        return (
+            isinstance(other, PolygonalCircuit)
+            and self._normalize().vertex_indices == other._normalize().vertex_indices
+        )
 
     def __hash__(self) -> int:
         # pylint: disable=protected-access
         return hash(self._normalize()._vertex_indices)
 
-    def adjacent_vertex_indices(self, vertices_num: int = 2
-                                ) -> typing.Iterable[typing.Tuple[int]]:
-        vertex_indices_cycle = list(itertools.islice(
-            itertools.cycle(self.vertex_indices),
-            0,
-            len(self.vertex_indices) + vertices_num - 1,
-        ))
-        return zip(*(itertools.islice(vertex_indices_cycle,
-                                      offset,
-                                      len(self.vertex_indices) + offset)
-                     for offset in range(vertices_num)))
+    def adjacent_vertex_indices(
+        self, vertices_num: int = 2
+    ) -> typing.Iterable[typing.Tuple[int]]:
+        vertex_indices_cycle = list(
+            itertools.islice(
+                itertools.cycle(self.vertex_indices),
+                0,
+                len(self.vertex_indices) + vertices_num - 1,
+            )
+        )
+        return zip(
+            *(
+                itertools.islice(
+                    vertex_indices_cycle, offset, len(self.vertex_indices) + offset
+                )
+                for offset in range(vertices_num)
+            )
+        )
 
 
 class LineSegment(PolygonalCircuit):
-
-    def __init__(self, indices: PolygonalCircuit._VERTEX_INDICES_TYPE):
+    def __init__(self, indices: typing.Iterable[int]):
         super().__init__(indices)
         assert len(self.vertex_indices) == 2
 
     def __repr__(self) -> str:
-        return 'LineSegment(vertex_indices={})'.format(self.vertex_indices)
+        return "LineSegment(vertex_indices={})".format(self.vertex_indices)
 
 
 class Triangle(PolygonalCircuit):
-
-    def __init__(self, indices: PolygonalCircuit._VERTEX_INDICES_TYPE):
+    def __init__(self, indices: typing.Iterable[int]):
         super().__init__(indices)
         assert len(self.vertex_indices) == 3
 
     def __repr__(self) -> str:
-        return 'Triangle(vertex_indices={})'.format(self.vertex_indices)
+        return "Triangle(vertex_indices={})".format(self.vertex_indices)
 
 
 class PolygonalChainsNotOverlapingError(ValueError):
@@ -178,18 +185,21 @@ class PolygonalChainsNotOverlapingError(ValueError):
 
 
 class PolygonalChain:
-
     def __init__(self, vertex_indices: typing.Iterable[int]):
-        self.vertex_indices \
-            = collections.deque(vertex_indices)  # type: Deque[int]
+        self.vertex_indices = collections.deque(
+            vertex_indices
+        )  # type: typing.Deque[int]
 
-    def __eq__(self, other: 'PolygonalChain') -> bool:
-        return self.vertex_indices == other.vertex_indices
+    def __eq__(self, other: object) -> bool:
+        return (
+            isinstance(other, PolygonalChain)
+            and self.vertex_indices == other.vertex_indices
+        )
 
     def __repr__(self) -> str:
-        return 'PolygonalChain(vertex_indices={})'.format(tuple(self.vertex_indices))
+        return "PolygonalChain(vertex_indices={})".format(tuple(self.vertex_indices))
 
-    def connect(self, other: 'PolygonalChain') -> None:
+    def connect(self, other: "PolygonalChain") -> None:
         if self.vertex_indices[-1] == other.vertex_indices[0]:
             self.vertex_indices.pop()
             self.vertex_indices.extend(other.vertex_indices)
@@ -205,12 +215,15 @@ class PolygonalChain:
         else:
             raise PolygonalChainsNotOverlapingError()
 
-    def adjacent_vertex_indices(self, vertices_num: int = 2
-                                ) -> typing.Iterable[typing.Tuple[int]]:
-        return zip(*(itertools.islice(self.vertex_indices,
-                                      offset,
-                                      len(self.vertex_indices))
-                     for offset in range(vertices_num)))
+    def adjacent_vertex_indices(
+        self, vertices_num: int = 2
+    ) -> typing.Iterator[typing.Tuple[int, ...]]:
+        return zip(
+            *(
+                itertools.islice(self.vertex_indices, offset, len(self.vertex_indices))
+                for offset in range(vertices_num)
+            )
+        )
 
     def segments(self) -> typing.Iterable[LineSegment]:
         return map(LineSegment, self.adjacent_vertex_indices(2))
@@ -219,8 +232,9 @@ class PolygonalChain:
 class Label:
 
     # pylint: disable=too-many-arguments
-    def __init__(self, index: int, name: str, red: int,
-                 green: int, blue: int, transparency: int):
+    def __init__(
+        self, index: int, name: str, red: int, green: int, blue: int, transparency: int
+    ):
         self.index = index  # type: int
         self.name = name  # type: str
         self.red = red  # type: int
@@ -232,16 +246,20 @@ class Label:
     def color_code(self) -> int:
         if self.index == 0:  # unknown
             return 0
-        return int.from_bytes((self.red, self.green, self.blue, self.transparency),
-                              byteorder='little', signed=False)
+        return int.from_bytes(
+            (self.red, self.green, self.blue, self.transparency),
+            byteorder="little",
+            signed=False,
+        )
 
     @property
     def hex_color_code(self) -> str:
-        return '#{:02x}{:02x}{:02x}'.format(self.red, self.green, self.blue)
+        return "#{:02x}{:02x}{:02x}".format(self.red, self.green, self.blue)
 
     def __str__(self) -> str:
-        return 'Label(name={}, index={}, color={})'.format(
-            self.name, self.index, self.hex_color_code)
+        return "Label(name={}, index={}, color={})".format(
+            self.name, self.index, self.hex_color_code
+        )
 
     def __repr__(self) -> str:
         return str(self)
@@ -251,7 +269,7 @@ class Annotation:
 
     # pylint: disable=too-few-public-methods
 
-    _TAG_OLD_COLORTABLE = b'\0\0\0\x01'
+    _TAG_OLD_COLORTABLE = b"\0\0\0\x01"
 
     def __init__(self):
         self.vertex_label_index = {}  # type: Dict[int, int]
@@ -260,38 +278,50 @@ class Annotation:
 
     @staticmethod
     def _read_label(stream: typing.BinaryIO) -> Label:
-        index, name_length = struct.unpack('>II', stream.read(4 * 2))
+        index, name_length = struct.unpack(">II", stream.read(4 * 2))
         name = stream.read(name_length - 1).decode()
-        assert stream.read(1) == b'\0'
-        red, green, blue, transparency \
-            = struct.unpack('>IIII', stream.read(4 * 4))
-        return Label(index=index, name=name, red=red, green=green,
-                     blue=blue, transparency=transparency)
+        assert stream.read(1) == b"\0"
+        red, green, blue, transparency = struct.unpack(">IIII", stream.read(4 * 4))
+        return Label(
+            index=index,
+            name=name,
+            red=red,
+            green=green,
+            blue=blue,
+            transparency=transparency,
+        )
 
     def _read(self, stream: typing.BinaryIO) -> None:
         # https://surfer.nmr.mgh.harvard.edu/fswiki/LabelsClutsAnnotationFiles
-        annotations_num, = struct.unpack('>I', stream.read(4))
-        annotations = [struct.unpack('>II', stream.read(4 * 2))
-                       for _ in range(annotations_num)]
+        (annotations_num,) = struct.unpack(">I", stream.read(4))
+        annotations = [
+            struct.unpack(">II", stream.read(4 * 2)) for _ in range(annotations_num)
+        ]
         assert stream.read(4) == self._TAG_OLD_COLORTABLE
-        colortable_version, _, filename_length \
-            = struct.unpack('>III', stream.read(4 * 3))
+        colortable_version, _, filename_length = struct.unpack(
+            ">III", stream.read(4 * 3)
+        )
         assert colortable_version > 0  # new version
         self.colortable_path = stream.read(filename_length - 1)
-        assert stream.read(1) == b'\0'
-        labels_num, = struct.unpack('>I', stream.read(4))
-        self.labels = {label.index: label for label
-                       in (self._read_label(stream) for _ in range(labels_num))}
-        label_index_by_color_code = {label.color_code: label.index
-                                     for label in self.labels.values()}
-        self.vertex_label_index = {vertex_index: label_index_by_color_code[color_code]
-                                   for vertex_index, color_code in annotations}
+        assert stream.read(1) == b"\0"
+        (labels_num,) = struct.unpack(">I", stream.read(4))
+        self.labels = {
+            label.index: label
+            for label in (self._read_label(stream) for _ in range(labels_num))
+        }
+        label_index_by_color_code = {
+            label.color_code: label.index for label in self.labels.values()
+        }
+        self.vertex_label_index = {
+            vertex_index: label_index_by_color_code[color_code]
+            for vertex_index, color_code in annotations
+        }
         assert not stream.read(1)
 
     @classmethod
-    def read(cls, annotation_file_path: str) -> 'Annotation':
+    def read(cls, annotation_file_path: str) -> "Annotation":
         annotation = cls()
-        with open(annotation_file_path, 'rb') as annotation_file:
+        with open(annotation_file_path, "rb") as annotation_file:
             # pylint: disable=protected-access
             annotation._read(annotation_file)
         return annotation
@@ -301,13 +331,13 @@ class Surface:
 
     # pylint: disable=too-many-instance-attributes
 
-    _MAGIC_NUMBER = b'\xff\xff\xfe'
+    _MAGIC_NUMBER = b"\xff\xff\xfe"
 
-    _TAG_CMDLINE = b'\x00\x00\x00\x03'
-    _TAG_OLD_SURF_GEOM = b'\x00\x00\x00\x14'
-    _TAG_OLD_USEREALRAS = b'\x00\x00\x00\x02'
+    _TAG_CMDLINE = b"\x00\x00\x00\x03"
+    _TAG_OLD_SURF_GEOM = b"\x00\x00\x00\x14"
+    _TAG_OLD_USEREALRAS = b"\x00\x00\x00\x02"
 
-    _DATETIME_FORMAT = '%a %b %d %H:%M:%S %Y'
+    _DATETIME_FORMAT = "%a %b %d %H:%M:%S %Y"
 
     def __init__(self):
         self.creator = None  # type: Optional[bytes]
@@ -320,7 +350,7 @@ class Surface:
         self.annotation = None  # type: Optional[Annotation]
 
     @classmethod
-    def _read_cmdlines(cls, stream: typing.BinaryIO) -> typing.Iterator[str]:
+    def _read_cmdlines(cls, stream: typing.BinaryIO) -> typing.Iterator[bytes]:
         while True:
             tag = stream.read(4)
             if not tag:
@@ -328,30 +358,40 @@ class Surface:
             assert tag == cls._TAG_CMDLINE  # might be TAG_GROUP_AVG_SURFACE_AREA
             # TAGwrite
             # https://github.com/freesurfer/freesurfer/blob/release_6_0_0/utils/tags.c#L94
-            str_length, = struct.unpack('>Q', stream.read(8))
+            (str_length,) = struct.unpack(">Q", stream.read(8))
             yield stream.read(str_length - 1)
-            assert stream.read(1) == b'\x00'
+            assert stream.read(1) == b"\x00"
 
     def _read_triangular(self, stream: typing.BinaryIO):
         assert stream.read(3) == self._MAGIC_NUMBER
-        self.creator, creation_dt_str = re.match(rb'^created by (\w+) on (.* \d{4})\n',
-                                                 stream.readline()).groups()
-        with setlocale('C'):
-            self.creation_datetime = datetime.datetime.strptime(creation_dt_str.decode(),
-                                                                self._DATETIME_FORMAT)
-        assert stream.read(1) == b'\n'
+        creation_match = re.match(
+            rb"^created by (\w+) on (.* \d{4})\n", stream.readline()
+        )
+        assert creation_match
+        self.creator, creation_dt_str = creation_match.groups()
+        with setlocale("C"):
+            self.creation_datetime = datetime.datetime.strptime(
+                creation_dt_str.decode(), self._DATETIME_FORMAT
+            )
+        assert stream.read(1) == b"\n"
         # fwriteInt
         # https://github.com/freesurfer/freesurfer/blob/release_6_0_0/utils/fio.c#L290
-        vertices_num, triangles_num = struct.unpack('>II', stream.read(4 * 2))
-        self.vertices = [Vertex(*struct.unpack('>fff', stream.read(4 * 3)))
-                         for _ in range(vertices_num)]
-        self.triangles = [Triangle(struct.unpack('>III', stream.read(4 * 3)))
-                          for _ in range(triangles_num)]
-        assert all(vertex_idx < vertices_num
-                   for triangle in self.triangles
-                   for vertex_idx in triangle.vertex_indices)
+        vertices_num, triangles_num = struct.unpack(">II", stream.read(4 * 2))
+        self.vertices = [
+            Vertex(*struct.unpack(">fff", stream.read(4 * 3)))
+            for _ in range(vertices_num)
+        ]
+        self.triangles = [
+            Triangle(struct.unpack(">III", stream.read(4 * 3)))
+            for _ in range(triangles_num)
+        ]
+        assert all(
+            vertex_idx < vertices_num
+            for triangle in self.triangles
+            for vertex_idx in triangle.vertex_indices
+        )
         assert stream.read(4) == self._TAG_OLD_USEREALRAS
-        using_old_real_ras, = struct.unpack('>I', stream.read(4))
+        (using_old_real_ras,) = struct.unpack(">I", stream.read(4))
         assert using_old_real_ras in [0, 1], using_old_real_ras
         self.using_old_real_ras = bool(using_old_real_ras)
         assert stream.read(4) == self._TAG_OLD_SURF_GEOM
@@ -361,46 +401,59 @@ class Surface:
         self.command_lines = list(self._read_cmdlines(stream))
 
     @classmethod
-    def read_triangular(cls, surface_file_path: str) -> 'Surface':
+    def read_triangular(cls, surface_file_path: str) -> "Surface":
         surface = cls()
-        with open(surface_file_path, 'rb') as surface_file:
+        with open(surface_file_path, "rb") as surface_file:
             # pylint: disable=protected-access
             surface._read_triangular(surface_file)
         return surface
 
     @classmethod
     def _triangular_strftime(cls, creation_datetime: datetime.datetime) -> bytes:
-        padded_day = '{:>2}'.format(creation_datetime.day)
-        fmt = cls._DATETIME_FORMAT.replace('%d', padded_day)
-        with setlocale('C'):
+        padded_day = "{:>2}".format(creation_datetime.day)
+        fmt = cls._DATETIME_FORMAT.replace("%d", padded_day)
+        with setlocale("C"):
             return creation_datetime.strftime(fmt).encode()
 
-    def write_triangular(self, surface_file_path: str,
-                         creation_datetime: typing.Optional[datetime.datetime] = None):
+    def write_triangular(
+        self,
+        surface_file_path: str,
+        creation_datetime: typing.Optional[datetime.datetime] = None,
+    ):
         if creation_datetime is None:
             creation_datetime = datetime.datetime.now()
-        with open(surface_file_path, 'wb') as surface_file:
+        with open(surface_file_path, "wb") as surface_file:
             surface_file.write(
                 self._MAGIC_NUMBER
-                + b'created by ' + self.creator
-                + b' on ' + self._triangular_strftime(creation_datetime)
-                + b'\n\n'
-                + struct.pack('>II', len(self.vertices), len(self.triangles))
+                + b"created by "
+                + self.creator
+                + b" on "
+                + self._triangular_strftime(creation_datetime)
+                + b"\n\n"
+                + struct.pack(">II", len(self.vertices), len(self.triangles))
             )
             for vertex in self.vertices:
-                surface_file.write(struct.pack('>fff', *vertex))
+                surface_file.write(struct.pack(">fff", *vertex))
             for triangle in self.triangles:
-                assert all(vertex_index < len(self.vertices)
-                           for vertex_index in triangle.vertex_indices)
-                surface_file.write(struct.pack('>III',
-                                               *triangle.vertex_indices))
-            surface_file.write(self._TAG_OLD_USEREALRAS
-                               + struct.pack('>I', 1 if self.using_old_real_ras else 0))
-            surface_file.write(self._TAG_OLD_SURF_GEOM
-                               + b''.join(self.volume_geometry_info))
+                assert all(
+                    vertex_index < len(self.vertices)
+                    for vertex_index in triangle.vertex_indices
+                )
+                surface_file.write(struct.pack(">III", *triangle.vertex_indices))
+            surface_file.write(
+                self._TAG_OLD_USEREALRAS
+                + struct.pack(">I", 1 if self.using_old_real_ras else 0)
+            )
+            surface_file.write(
+                self._TAG_OLD_SURF_GEOM + b"".join(self.volume_geometry_info)
+            )
             for command_line in self.command_lines:
-                surface_file.write(self._TAG_CMDLINE + struct.pack('>Q', len(command_line) + 1)
-                                   + command_line + b'\0')
+                surface_file.write(
+                    self._TAG_CMDLINE
+                    + struct.pack(">Q", len(command_line) + 1)
+                    + command_line
+                    + b"\0"
+                )
 
     def load_annotation_file(self, annotation_file_path: str) -> None:
         annotation = Annotation.read(annotation_file_path)
@@ -412,23 +465,27 @@ class Surface:
         self.vertices.append(vertex)
         return len(self.vertices) - 1
 
-    def add_rectangle(self, vertex_indices: typing.Iterable[int]) -> typing.Iterable[int]:
+    def add_rectangle(self, vertex_indices: typing.Iterable[int]) -> None:
         vertex_indices = list(vertex_indices)
         if len(vertex_indices) == 3:
-            vertex_indices.append(self.add_vertex(
-                self.vertices[vertex_indices[0]]
-                + self.vertices[vertex_indices[2]]
-                - self.vertices[vertex_indices[1]]
-            ))
+            vertex_indices.append(
+                self.add_vertex(
+                    self.vertices[vertex_indices[0]]
+                    + self.vertices[vertex_indices[2]]
+                    - self.vertices[vertex_indices[1]]
+                )
+            )
         assert len(vertex_indices) == 4
         self.triangles.append(Triangle(vertex_indices[:3]))
-        self.triangles.append(Triangle(vertex_indices[2:]
-                                       + vertex_indices[:1]))
-
-    def _triangle_count_by_adjacent_vertex_indices(self) \
-            -> typing.Dict[int, typing.Dict[int, int]]:
-        counts = {vertex_index: collections.defaultdict(lambda: 0)
-                  for vertex_index in range(len(self.vertices))}
+        self.triangles.append(Triangle(vertex_indices[2:] + vertex_indices[:1]))
+
+    def _triangle_count_by_adjacent_vertex_indices(
+        self,
+    ) -> typing.Dict[int, typing.DefaultDict[int, int]]:
+        counts = {
+            vertex_index: collections.defaultdict(lambda: 0)
+            for vertex_index in range(len(self.vertices))
+        }  # type: typing.Dict[int, typing.DefaultDict[int, int]]
         for triangle in self.triangles:
             for vertex_index_pair in triangle.adjacent_vertex_indices(2):
                 counts[vertex_index_pair[0]][vertex_index_pair[1]] += 1
@@ -437,17 +494,20 @@ class Surface:
 
     def find_borders(self) -> typing.Iterator[PolygonalCircuit]:
         border_neighbours = {}
-        for vertex_index, neighbour_counts \
-                in self._triangle_count_by_adjacent_vertex_indices().items():
+        for (
+            vertex_index,
+            neighbour_counts,
+        ) in self._triangle_count_by_adjacent_vertex_indices().items():
             if not neighbour_counts:
                 yield PolygonalCircuit((vertex_index,))
             else:
-                neighbours = [neighbour_index for neighbour_index, counts
-                              in neighbour_counts.items()
-                              if counts != 2]
+                neighbours = [
+                    neighbour_index
+                    for neighbour_index, counts in neighbour_counts.items()
+                    if counts != 2
+                ]
                 if neighbours:
-                    assert len(neighbours) % 2 == 0, \
-                        (vertex_index, neighbour_counts)
+                    assert len(neighbours) % 2 == 0, (vertex_index, neighbour_counts)
                     border_neighbours[vertex_index] = neighbours
         while border_neighbours:
             vertex_index, neighbour_indices = border_neighbours.popitem()
@@ -461,11 +521,17 @@ class Surface:
                 if len(neighbour_indices) > 1:
                     border_neighbours[vertex_index] = neighbour_indices[1:]
                 vertex_index = neighbour_indices[0]
-            assert vertex_index in border_neighbours, \
-                (vertex_index, cycle_indices, border_neighbours)
+            assert vertex_index in border_neighbours, (
+                vertex_index,
+                cycle_indices,
+                border_neighbours,
+            )
             final_neighbour_indices = border_neighbours.pop(vertex_index)
-            assert final_neighbour_indices == [cycle_indices[-1]], \
-                (vertex_index, final_neighbour_indices, cycle_indices)
+            assert final_neighbour_indices == [cycle_indices[-1]], (
+                vertex_index,
+                final_neighbour_indices,
+                cycle_indices,
+            )
             yield PolygonalCircuit(cycle_indices)
 
     def _get_vertex_label_index(self, vertex_index: int) -> typing.Optional[int]:
@@ -473,17 +539,22 @@ class Surface:
 
     def _find_label_border_segments(self, label: Label) -> typing.Iterator[LineSegment]:
         for triangle in self.triangles:
-            border_vertex_indices = tuple(filter(
-                lambda i: self._get_vertex_label_index(i) == label.index,
-                triangle.vertex_indices,
-            ))
+            border_vertex_indices = tuple(
+                filter(
+                    lambda i: self._get_vertex_label_index(i) == label.index,
+                    triangle.vertex_indices,
+                )
+            )
             if len(border_vertex_indices) == 2:
                 yield LineSegment(border_vertex_indices)
 
-    def find_label_border_polygonal_chains(self, label: Label) -> typing.Iterator[PolygonalChain]:
+    def find_label_border_polygonal_chains(
+        self, label: Label
+    ) -> typing.Iterator[PolygonalChain]:
         segments = set(self._find_label_border_segments(label))
-        available_chains = collections.deque(PolygonalChain(segment.vertex_indices)
-                                             for segment in segments)
+        available_chains = collections.deque(
+            PolygonalChain(segment.vertex_indices) for segment in segments
+        )
         # irrespective of its poor performance,
         # we keep this approach since it's easy to read and fast enough
         while available_chains:
@@ -491,7 +562,9 @@ class Surface:
             last_chains_len = None
             while last_chains_len != len(available_chains):
                 last_chains_len = len(available_chains)
-                checked_chains = collections.deque()
+                checked_chains = (
+                    collections.deque()
+                )  # type: typing.Deque[PolygonalChain]
                 while available_chains:
                     potential_neighbour = available_chains.pop()
                     try:
@@ -516,23 +589,30 @@ class Surface:
             vertex_index_conversion[vertex_index] -= 1
         vertex_index_conversion = numpy.cumsum(vertex_index_conversion)
         for triangle_index in range(len(self.triangles)):
-            self.triangles[triangle_index] \
-                = Triangle(map(lambda i: i + int(vertex_index_conversion[i]),
-                               self.triangles[triangle_index].vertex_indices))
+            self.triangles[triangle_index] = Triangle(
+                map(
+                    lambda i: i + int(vertex_index_conversion[i]),
+                    self.triangles[triangle_index].vertex_indices,
+                )
+            )
 
-    def select_vertices(self, vertex_indices: typing.Iterable[int]) \
-            -> typing.List[Vertex]:
+    def select_vertices(
+        self, vertex_indices: typing.Iterable[int]
+    ) -> typing.List[Vertex]:
         return [self.vertices[idx] for idx in vertex_indices]
 
     @staticmethod
-    def unite(surfaces: typing.Iterable['Surface']) -> 'Surface':
+    def unite(surfaces: typing.Iterable["Surface"]) -> "Surface":
         surfaces_iter = iter(surfaces)
         union = copy.deepcopy(next(surfaces_iter))
         for surface in surfaces_iter:
             vertex_index_offset = len(union.vertices)
             union.vertices.extend(surface.vertices)
             union.triangles.extend(
-                Triangle(vertex_idx + vertex_index_offset
-                         for vertex_idx in triangle.vertex_indices)
-                for triangle in surface.triangles)
+                Triangle(
+                    vertex_idx + vertex_index_offset
+                    for vertex_idx in triangle.vertex_indices
+                )
+                for triangle in surface.triangles
+            )
         return union

+ 18 - 18
freesurfer_surface/__main__.py

@@ -10,17 +10,22 @@ def annotation_labels():
     List Labels Stored in Freesurfer's Annotation File
     (i.e., label/lh.aparc.annot)
     """
-    argparser = argparse.ArgumentParser(
-        description=annotation_labels.__doc__.strip())
-    argparser.add_argument('--delimiter', default='\t',
-                           help='default: %(default)r')
-    argparser.add_argument('annotation_file_path')
+    argparser = argparse.ArgumentParser(description=annotation_labels.__doc__.strip())
+    argparser.add_argument("--delimiter", default="\t", help="default: %(default)r")
+    argparser.add_argument("annotation_file_path")
     args = argparser.parse_args()
     annotation = Annotation.read(args.annotation_file_path)
     csv_writer = csv.writer(sys.stdout, delimiter=args.delimiter)
-    csv_writer.writerow(('index', 'color', 'name'))
+    csv_writer.writerow(("index", "color", "name"))
     labels = sorted(annotation.labels.values(), key=lambda l: l.index)
-    csv_writer.writerows((l.index, l.hex_color_code, l.name,) for l in labels)
+    csv_writer.writerows(
+        (
+            l.index,
+            l.hex_color_code,
+            l.name,
+        )
+        for l in labels
+    )
 
 
 def unite_surfaces():
@@ -28,16 +33,11 @@ def unite_surfaces():
     Unite Multiple Surfaces in Freesurfer's TriangularSurface Format
     Into a Single TriangularSurface File (i.e., lh.pial, lh.white)
     """
-    argparser = argparse.ArgumentParser(
-        description=unite_surfaces.__doc__.strip())
-    argparser.add_argument('--output',
-                           metavar='OUTPUT_PATH',
-                           dest='output_path',
-                           required=True)
-    argparser.add_argument('input_paths',
-                           metavar='INPUT_PATH',
-                           nargs='+')
+    argparser = argparse.ArgumentParser(description=unite_surfaces.__doc__.strip())
+    argparser.add_argument(
+        "--output", metavar="OUTPUT_PATH", dest="output_path", required=True
+    )
+    argparser.add_argument("input_paths", metavar="INPUT_PATH", nargs="+")
     args = argparser.parse_args()
-    union = Surface.unite(Surface.read_triangular(p)
-                          for p in args.input_paths)
+    union = Surface.unite(Surface.read_triangular(p) for p in args.input_paths)
     union.write_triangular(args.output_path)

+ 2 - 0
mypy.ini

@@ -0,0 +1,2 @@
+[mypy]
+ignore_missing_imports = True

+ 27 - 25
setup.py

@@ -2,32 +2,34 @@ import os
 
 import setuptools
 
-with open('README.rst', 'r') as readme:
+with open("README.rst", "r") as readme:
     LONG_DESCRIPTION = readme.read()
 
 setuptools.setup(
-    name='freesurfer-surface',
+    name="freesurfer-surface",
     use_scm_version={
-        'write_to': os.path.join('freesurfer_surface', 'version.py'),
+        "write_to": os.path.join("freesurfer_surface", "version.py"),
         # `version` triggers pylint C0103
-        'write_to_template': "__version__ = '{version}'\n",
+        # newline after import to fix pylint C0321/multiple-statements
+        "write_to_template": "import typing\n"
+        + "__version__ = '{version}' # type: typing.Optional[str]\n",
     },
     description="Python Library to Read and Write Surface Files"
-                " in Freesurfer's TriangularSurface Format",
+    " in Freesurfer's TriangularSurface Format",
     long_description=LONG_DESCRIPTION,
-    author='Fabian Peter Hammerle',
-    author_email='fabian@hammerle.me',
-    url='https://github.com/fphammerle/freesurfer-surface',
+    author="Fabian Peter Hammerle",
+    author_email="fabian@hammerle.me",
+    url="https://github.com/fphammerle/freesurfer-surface",
     # TODO add license
     keywords=[
-        'brain',
-        'freesurfer',
-        'mesh',
-        'neuroimaging',
-        'reader',
-        'surface',
-        'triangle',
-        'vertex',
+        "brain",
+        "freesurfer",
+        "mesh",
+        "neuroimaging",
+        "reader",
+        "surface",
+        "triangle",
+        "vertex",
     ],
     classifiers=[
         "Development Status :: 3 - Alpha",
@@ -42,21 +44,21 @@ setuptools.setup(
     ],
     packages=setuptools.find_packages(),
     entry_points={
-        'console_scripts': [
-            'freesurfer-annotation-labels = freesurfer_surface.__main__:annotation_labels',
-            'unite-freesurfer-surfaces = freesurfer_surface.__main__:unite_surfaces',
+        "console_scripts": [
+            "freesurfer-annotation-labels = freesurfer_surface.__main__:annotation_labels",
+            "unite-freesurfer-surfaces = freesurfer_surface.__main__:unite_surfaces",
         ],
     },
-    python_requires='>=3.5',
+    python_requires=">=3.5",
     install_requires=[
-        'numpy<2',
+        "numpy<2",
     ],
     setup_requires=[
-        'setuptools_scm',
+        "setuptools_scm",
     ],
     tests_require=[
-        'pylint>=2.3.0,<3',
-        'pytest<5',
-        'pytest-cov<3,>=2',
+        "pylint>=2.3.0,<3",
+        "pytest<5",
+        "pytest-cov<3,>=2",
     ],
 )

+ 3 - 4
tests/conftest.py

@@ -1,7 +1,6 @@
 import os
 
-SUBJECTS_DIR = os.path.join(os.path.dirname(__file__), 'subjects')
+SUBJECTS_DIR = os.path.join(os.path.dirname(__file__), "subjects")
 
-ANNOTATION_FILE_PATH = os.path.join(SUBJECTS_DIR, 'fabian',
-                                    'label', 'lh.aparc.annot')
-SURFACE_FILE_PATH = os.path.join(SUBJECTS_DIR, 'fabian', 'surf', 'lh.pial')
+ANNOTATION_FILE_PATH = os.path.join(SUBJECTS_DIR, "fabian", "label", "lh.aparc.annot")
+SURFACE_FILE_PATH = os.path.join(SUBJECTS_DIR, "fabian", "surf", "lh.pial")

+ 44 - 17
tests/test_annotation.py

@@ -14,21 +14,48 @@ def test_load_annotation():
     assert annotation.vertex_label_index[93859] == 28
     assert annotation.vertex_label_index[78572] == 0
     assert annotation.vertex_label_index[120377] == 0
-    assert annotation.colortable_path == b'/autofs/space/tanha_002/users/greve' \
-                                         b'/fsdev.build/average/colortable_desikan_killiany.txt'
+    assert (
+        annotation.colortable_path == b"/autofs/space/tanha_002/users/greve"
+        b"/fsdev.build/average/colortable_desikan_killiany.txt"
+    )
     assert len(annotation.labels) == 36
-    assert vars(annotation.labels[0]) == {'index': 0, 'name': 'unknown',
-                                          'red': 25, 'green': 5, 'blue': 25, 'transparency': 0}
-    assert vars(annotation.labels[28]) == {'index': 28, 'name': 'superiorfrontal',
-                                           'red': 20, 'green': 220, 'blue': 160, 'transparency': 0}
-    precentral, = filter(lambda l: l.name == 'precentral',
-                         annotation.labels.values())
-    postcentral, = filter(lambda l: l.name == 'postcentral',
-                          annotation.labels.values())
-    assert vars(precentral) == {'index': 24, 'name': 'precentral',
-                                'red': 60, 'green': 20, 'blue': 220, 'transparency': 0}
-    assert vars(postcentral) == {'index': 22, 'name': 'postcentral',
-                                 'red': 220, 'green': 20, 'blue': 20, 'transparency': 0}
-    superiorfrontal, = filter(lambda l: l.color_code == 10542100,
-                              annotation.labels.values())
-    assert superiorfrontal.name == 'superiorfrontal'
+    assert vars(annotation.labels[0]) == {
+        "index": 0,
+        "name": "unknown",
+        "red": 25,
+        "green": 5,
+        "blue": 25,
+        "transparency": 0,
+    }
+    assert vars(annotation.labels[28]) == {
+        "index": 28,
+        "name": "superiorfrontal",
+        "red": 20,
+        "green": 220,
+        "blue": 160,
+        "transparency": 0,
+    }
+    (precentral,) = filter(lambda l: l.name == "precentral", annotation.labels.values())
+    (postcentral,) = filter(
+        lambda l: l.name == "postcentral", annotation.labels.values()
+    )
+    assert vars(precentral) == {
+        "index": 24,
+        "name": "precentral",
+        "red": 60,
+        "green": 20,
+        "blue": 220,
+        "transparency": 0,
+    }
+    assert vars(postcentral) == {
+        "index": 22,
+        "name": "postcentral",
+        "red": 220,
+        "green": 20,
+        "blue": 20,
+        "transparency": 0,
+    }
+    (superiorfrontal,) = filter(
+        lambda l: l.color_code == 10542100, annotation.labels.values()
+    )
+    assert superiorfrontal.name == "superiorfrontal"

+ 43 - 31
tests/test_label.py

@@ -3,50 +3,62 @@ import pytest
 from freesurfer_surface import Label
 
 
-@pytest.mark.parametrize(('red', 'green', 'blue', 'transparency', 'color_code'), [
-    # pylint: disable=bad-whitespace
-    (220,  20,  20,   0,  1316060),
-    ( 60,  20, 220,   0, 14423100),
-    ( 75,  50, 125,   0,  8204875),
-    ( 20, 220, 160,   0, 10542100),
-])
+@pytest.mark.parametrize(
+    ("red", "green", "blue", "transparency", "color_code"),
+    [
+        (220, 20, 20, 0, 1316060),
+        (60, 20, 220, 0, 14423100),
+        (75, 50, 125, 0, 8204875),
+        (20, 220, 160, 0, 10542100),
+    ],
+)
 def test_color_code(red, green, blue, transparency, color_code):
-    label = Label(index=21, name='name', red=red, green=green,
-                  blue=blue, transparency=transparency)
+    label = Label(
+        index=21,
+        name="name",
+        red=red,
+        green=green,
+        blue=blue,
+        transparency=transparency,
+    )
     assert color_code == label.color_code
 
 
 def test_color_code_unknown():
-    label = Label(index=0, name='unknown', red=21,
-                  green=21, blue=21, transparency=0)
+    label = Label(index=0, name="unknown", red=21, green=21, blue=21, transparency=0)
     assert label.color_code == 0
 
 
-@pytest.mark.parametrize(('red', 'green', 'blue', 'hex_color_code'), [
-    # pylint: disable=bad-whitespace
-    (  0,   0,   0, '#000000'),
-    (255, 255, 255, '#ffffff'),
-    (255,   0,   0, '#ff0000'),
-    (  0, 255,   0, '#00ff00'),
-    (  0,   0, 255, '#0000ff'),
-    (  1,   2,   3, '#010203'),
-    ( 17,  18,  19, '#111213'),
-    (128, 192, 255, '#80c0ff'),
-    ( 20, 220, 160, '#14dca0'),
-])
+@pytest.mark.parametrize(
+    ("red", "green", "blue", "hex_color_code"),
+    [
+        (0, 0, 0, "#000000"),
+        (255, 255, 255, "#ffffff"),
+        (255, 0, 0, "#ff0000"),
+        (0, 255, 0, "#00ff00"),
+        (0, 0, 255, "#0000ff"),
+        (1, 2, 3, "#010203"),
+        (17, 18, 19, "#111213"),
+        (128, 192, 255, "#80c0ff"),
+        (20, 220, 160, "#14dca0"),
+    ],
+)
 def test_hex_color_code(red, green, blue, hex_color_code):
-    label = Label(index=21, name='name', red=red,
-                  green=green, blue=blue, transparency=0)
+    label = Label(
+        index=21, name="name", red=red, green=green, blue=blue, transparency=0
+    )
     assert hex_color_code == label.hex_color_code.lower()
 
 
 def test_str():
-    label = Label(index=24, name='precentral', red=60,
-                  green=20, blue=220, transparency=0)
-    assert str(label) == 'Label(name=precentral, index=24, color=#3c14dc)'
+    label = Label(
+        index=24, name="precentral", red=60, green=20, blue=220, transparency=0
+    )
+    assert str(label) == "Label(name=precentral, index=24, color=#3c14dc)"
 
 
 def test_repr():
-    label = Label(index=24, name='precentral', red=60,
-                  green=20, blue=220, transparency=0)
-    assert repr(label) == 'Label(name=precentral, index=24, color=#3c14dc)'
+    label = Label(
+        index=24, name="precentral", red=60, green=20, blue=220, transparency=0
+    )
+    assert repr(label) == "Label(name=precentral, index=24, color=#3c14dc)"

+ 4 - 2
tests/test_line_segment.py

@@ -15,8 +15,10 @@ def test_eq():
 
 
 def test_repr():
-    assert repr(LineSegment((67018, 67019))) \
-        == 'LineSegment(vertex_indices=(67018, 67019))'
+    assert (
+        repr(LineSegment((67018, 67019)))
+        == "LineSegment(vertex_indices=(67018, 67019))"
+    )
 
 
 def test_adjacent_vertex_indices_1():

+ 37 - 25
tests/test_main.py

@@ -13,34 +13,39 @@ from freesurfer_surface.__main__ import annotation_labels, unite_surfaces
 
 def check_rows(csv_rows: typing.List[str]):
     assert len(csv_rows) == 36 + 1
-    assert csv_rows[0] == ['index', 'color', 'name']
-    assert csv_rows[1] == ['0', '#190519', 'unknown']
-    assert csv_rows[23] == ['22', '#dc1414', 'postcentral']
-    assert csv_rows[25] == ['24', '#3c14dc', 'precentral']
+    assert csv_rows[0] == ["index", "color", "name"]
+    assert csv_rows[1] == ["0", "#190519", "unknown"]
+    assert csv_rows[23] == ["22", "#dc1414", "postcentral"]
+    assert csv_rows[25] == ["24", "#3c14dc", "precentral"]
 
 
 def test_annotation_labels_function(capsys):
-    with unittest.mock.patch('sys.argv', ['', ANNOTATION_FILE_PATH]):
+    with unittest.mock.patch("sys.argv", ["", ANNOTATION_FILE_PATH]):
         annotation_labels()
     out, err = capsys.readouterr()
     assert not err
-    check_rows(list(csv.reader(io.StringIO(out), delimiter='\t')))
+    check_rows(list(csv.reader(io.StringIO(out), delimiter="\t")))
 
 
 def test_annotation_labels_function_delimiter(capsys):
-    with unittest.mock.patch('sys.argv', ['', '--delimiter', ',', ANNOTATION_FILE_PATH]):
+    with unittest.mock.patch(
+        "sys.argv", ["", "--delimiter", ",", ANNOTATION_FILE_PATH]
+    ):
         annotation_labels()
     out, err = capsys.readouterr()
     assert not err
-    check_rows(list(csv.reader(io.StringIO(out), delimiter=',')))
+    check_rows(list(csv.reader(io.StringIO(out), delimiter=",")))
 
 
 def test_annotation_labels_script():
-    proc_info = subprocess.run(['freesurfer-annotation-labels', ANNOTATION_FILE_PATH],
-                               check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+    proc_info = subprocess.run(
+        ["freesurfer-annotation-labels", ANNOTATION_FILE_PATH],
+        check=True,
+        stdout=subprocess.PIPE,
+        stderr=subprocess.PIPE,
+    )
     assert not proc_info.stderr
-    check_rows(list(csv.reader(io.StringIO(proc_info.stdout.decode()),
-                               delimiter='\t')))
+    check_rows(list(csv.reader(io.StringIO(proc_info.stdout.decode()), delimiter="\t")))
 
 
 def test_unite_surfaces_function(tmpdir, capsys):
@@ -49,11 +54,12 @@ def test_unite_surfaces_function(tmpdir, capsys):
     for i in range(5):
         surface_b.add_vertex(Vertex(i, i, i))
     surface_b.triangles = [Triangle((0, 1, 3)), Triangle((1, 3, 4))]
-    surface_b_path = tmpdir.join('b').strpath
+    surface_b_path = tmpdir.join("b").strpath
     surface_b.write_triangular(surface_b_path)
-    output_path = tmpdir.join('output_path').strpath
-    with unittest.mock.patch('sys.argv', ['', '--output', output_path,
-                                          SURFACE_FILE_PATH, surface_b_path]):
+    output_path = tmpdir.join("output_path").strpath
+    with unittest.mock.patch(
+        "sys.argv", ["", "--output", output_path, SURFACE_FILE_PATH, surface_b_path]
+    ):
         unite_surfaces()
     out, err = capsys.readouterr()
     assert not out
@@ -70,16 +76,22 @@ def test_unite_surfaces_script(tmpdir):
     for i in range(5):
         surface_b.add_vertex(Vertex(i, i, i))
     surface_b.triangles = [Triangle((0, 1, 3)), Triangle((1, 3, 4))]
-    surface_b_path = tmpdir.join('b').strpath
+    surface_b_path = tmpdir.join("b").strpath
     surface_b.write_triangular(surface_b_path)
-    output_path = tmpdir.join('output_path').strpath
-    proc_info = subprocess.run(['unite-freesurfer-surfaces',
-                                '--output', output_path,
-                                SURFACE_FILE_PATH, surface_b_path,
-                                surface_b_path],
-                               check=True,
-                               stdout=subprocess.PIPE,
-                               stderr=subprocess.PIPE)
+    output_path = tmpdir.join("output_path").strpath
+    proc_info = subprocess.run(
+        [
+            "unite-freesurfer-surfaces",
+            "--output",
+            output_path,
+            SURFACE_FILE_PATH,
+            surface_b_path,
+            surface_b_path,
+        ],
+        check=True,
+        stdout=subprocess.PIPE,
+        stderr=subprocess.PIPE,
+    )
     assert not proc_info.stdout
     assert not proc_info.stderr
     union = Surface.read_triangular(output_path)

+ 40 - 28
tests/test_polygonal_chain.py

@@ -1,7 +1,10 @@
 import pytest
 
-from freesurfer_surface import (LineSegment, PolygonalChain,
-                                PolygonalChainsNotOverlapingError)
+from freesurfer_surface import (
+    LineSegment,
+    PolygonalChain,
+    PolygonalChainsNotOverlapingError,
+)
 
 
 def test_init():
@@ -25,44 +28,53 @@ def test_eq():
 
 
 def test_repr():
-    assert repr(PolygonalChain([])) \
-        == 'PolygonalChain(vertex_indices=())'
-    assert repr(PolygonalChain((0, 2, 1))) \
-        == 'PolygonalChain(vertex_indices=(0, 2, 1))'
-    assert repr(PolygonalChain((0, 2, 1, 4, 3))) \
-        == 'PolygonalChain(vertex_indices=(0, 2, 1, 4, 3))'
-
-
-@pytest.mark.parametrize(('vertex_indices_a', 'vertex_indices_b', 'expected_vertex_indices'), [
-    ((1, 2, 3), (3, 4), (1, 2, 3, 4)),
-    ((1, 2, 3), (4, 3), (1, 2, 3, 4)),
-    ((3, 2, 1), (3, 4), (4, 3, 2, 1)),
-    ((3, 2, 1), (4, 3), (4, 3, 2, 1)),
-    ((1,), (1,), (1,)),
-    ((1, 2), (1,), (1, 2)),
-    ((1, 2), (2,), (1, 2)),
-    ((0, 3, 1, 5, 2), (3, 5, 2, 0), (3, 5, 2, 0, 3, 1, 5, 2)),
-    ((98792, 98807, 98821), (98792, 98793), (98793, 98792, 98807, 98821)),
-])
+    assert repr(PolygonalChain([])) == "PolygonalChain(vertex_indices=())"
+    assert repr(PolygonalChain((0, 2, 1))) == "PolygonalChain(vertex_indices=(0, 2, 1))"
+    assert (
+        repr(PolygonalChain((0, 2, 1, 4, 3)))
+        == "PolygonalChain(vertex_indices=(0, 2, 1, 4, 3))"
+    )
+
+
+@pytest.mark.parametrize(
+    ("vertex_indices_a", "vertex_indices_b", "expected_vertex_indices"),
+    [
+        ((1, 2, 3), (3, 4), (1, 2, 3, 4)),
+        ((1, 2, 3), (4, 3), (1, 2, 3, 4)),
+        ((3, 2, 1), (3, 4), (4, 3, 2, 1)),
+        ((3, 2, 1), (4, 3), (4, 3, 2, 1)),
+        ((1,), (1,), (1,)),
+        ((1, 2), (1,), (1, 2)),
+        ((1, 2), (2,), (1, 2)),
+        ((0, 3, 1, 5, 2), (3, 5, 2, 0), (3, 5, 2, 0, 3, 1, 5, 2)),
+        ((98792, 98807, 98821), (98792, 98793), (98793, 98792, 98807, 98821)),
+    ],
+)
 def test_connect(vertex_indices_a, vertex_indices_b, expected_vertex_indices):
     chain = PolygonalChain(vertex_indices_a)
     chain.connect(PolygonalChain(vertex_indices_b))
     assert PolygonalChain(expected_vertex_indices) == chain
 
 
-@pytest.mark.parametrize(('vertex_indices_a', 'vertex_indices_b'), [
-    ((1, 2, 3), (2, 4)),
-])
+@pytest.mark.parametrize(
+    ("vertex_indices_a", "vertex_indices_b"),
+    [
+        ((1, 2, 3), (2, 4)),
+    ],
+)
 def test_connect_fail(vertex_indices_a, vertex_indices_b):
     chain = PolygonalChain(vertex_indices_a)
     with pytest.raises(PolygonalChainsNotOverlapingError):
         chain.connect(PolygonalChain(vertex_indices_b))
 
 
-@pytest.mark.parametrize(('vertex_indices_a', 'vertex_indices_b'), [
-    ((1, 2, 3), ()),
-    ((), (3, 4)),
-])
+@pytest.mark.parametrize(
+    ("vertex_indices_a", "vertex_indices_b"),
+    [
+        ((1, 2, 3), ()),
+        ((), (3, 4)),
+    ],
+)
 def test_connect_fail_empty(vertex_indices_a, vertex_indices_b):
     chain = PolygonalChain(vertex_indices_a)
     with pytest.raises(Exception):

+ 30 - 38
tests/test_polygonal_circuit.py

@@ -27,22 +27,25 @@ def test_init_invalid_type():
         PolygonalCircuit((0, 1, Vertex(2, 3, 4)))
 
 
-@pytest.mark.parametrize(('source_vertex_indices', 'expected_vertex_indices'), [
-    ((1,), (1,)),
-    ((1, 2), (1, 2)),
-    ((2, 1), (1, 2)),
-    ((1, 2, 3), (1, 2, 3)),
-    ((2, 3, 1), (1, 2, 3)),
-    ((3, 1, 2), (1, 2, 3)),
-    ((1, 3, 2), (1, 2, 3)),
-    ((2, 1, 3), (1, 2, 3)),
-    ((3, 2, 1), (1, 2, 3)),
-    ((1, 2, 3, 5), (1, 2, 3, 5)),
-    ((2, 3, 5, 1), (1, 2, 3, 5)),
-    ((3, 5, 1, 2), (1, 2, 3, 5)),
-    ((2, 1, 5, 3), (1, 2, 3, 5)),
-    ((5, 3, 2, 1), (1, 2, 3, 5)),
-])
+@pytest.mark.parametrize(
+    ("source_vertex_indices", "expected_vertex_indices"),
+    [
+        ((1,), (1,)),
+        ((1, 2), (1, 2)),
+        ((2, 1), (1, 2)),
+        ((1, 2, 3), (1, 2, 3)),
+        ((2, 3, 1), (1, 2, 3)),
+        ((3, 1, 2), (1, 2, 3)),
+        ((1, 3, 2), (1, 2, 3)),
+        ((2, 1, 3), (1, 2, 3)),
+        ((3, 2, 1), (1, 2, 3)),
+        ((1, 2, 3, 5), (1, 2, 3, 5)),
+        ((2, 3, 5, 1), (1, 2, 3, 5)),
+        ((3, 5, 1, 2), (1, 2, 3, 5)),
+        ((2, 1, 5, 3), (1, 2, 3, 5)),
+        ((5, 3, 2, 1), (1, 2, 3, 5)),
+    ],
+)
 def test__normalize(source_vertex_indices, expected_vertex_indices):
     # pylint: disable=protected-access
     circuit = PolygonalCircuit(source_vertex_indices)
@@ -69,31 +72,20 @@ def test_eq_reverse():
 
 
 def test_hash():
-    assert hash(PolygonalCircuit((0, 1, 2))) \
-        == hash(PolygonalCircuit((0, 1, 2)))
-    assert hash(PolygonalCircuit((0, 1, 2))) \
-        == hash(PolygonalCircuit((1, 2, 0)))
-    assert hash(PolygonalCircuit((0, 1, 2))) \
-        == hash(PolygonalCircuit((2, 0, 1)))
-    assert hash(PolygonalCircuit((0, 1, 2))) \
-        != hash(PolygonalCircuit((0, 1, 4)))
-    assert hash(PolygonalCircuit((0, 1, 2))) \
-        != hash(PolygonalCircuit((0, 4, 2)))
-    assert hash(PolygonalCircuit((0, 1, 2))) \
-        != hash(PolygonalCircuit((4, 1, 2)))
+    assert hash(PolygonalCircuit((0, 1, 2))) == hash(PolygonalCircuit((0, 1, 2)))
+    assert hash(PolygonalCircuit((0, 1, 2))) == hash(PolygonalCircuit((1, 2, 0)))
+    assert hash(PolygonalCircuit((0, 1, 2))) == hash(PolygonalCircuit((2, 0, 1)))
+    assert hash(PolygonalCircuit((0, 1, 2))) != hash(PolygonalCircuit((0, 1, 4)))
+    assert hash(PolygonalCircuit((0, 1, 2))) != hash(PolygonalCircuit((0, 4, 2)))
+    assert hash(PolygonalCircuit((0, 1, 2))) != hash(PolygonalCircuit((4, 1, 2)))
 
 
 def test_hash_reverse():
-    assert hash(PolygonalCircuit((0, 1, 2))) \
-        == hash(PolygonalCircuit((2, 1, 0)))
-    assert hash(PolygonalCircuit((0, 1, 2))) \
-        == hash(PolygonalCircuit((0, 2, 1)))
-    assert hash(PolygonalCircuit((0, 1, 2, 4))) \
-        == hash(PolygonalCircuit((4, 2, 1, 0)))
-    assert hash(PolygonalCircuit((0, 1, 2, 4))) \
-        == hash(PolygonalCircuit((1, 0, 4, 2)))
-    assert hash(PolygonalCircuit((0, 1, 2, 4))) \
-        != hash(PolygonalCircuit((1, 4, 0, 2)))
+    assert hash(PolygonalCircuit((0, 1, 2))) == hash(PolygonalCircuit((2, 1, 0)))
+    assert hash(PolygonalCircuit((0, 1, 2))) == hash(PolygonalCircuit((0, 2, 1)))
+    assert hash(PolygonalCircuit((0, 1, 2, 4))) == hash(PolygonalCircuit((4, 2, 1, 0)))
+    assert hash(PolygonalCircuit((0, 1, 2, 4))) == hash(PolygonalCircuit((1, 0, 4, 2)))
+    assert hash(PolygonalCircuit((0, 1, 2, 4))) != hash(PolygonalCircuit((1, 4, 0, 2)))
 
 
 def test_adjacent_vertex_indices_1():

+ 4 - 4
tests/test_setlocale.py

@@ -7,16 +7,16 @@ from freesurfer_surface import UnsupportedLocaleSettingError, setlocale
 
 def test_set():
     system_locale = locale.setlocale(locale.LC_ALL)
-    assert system_locale != 'C'
-    with setlocale('C'):
-        assert locale.setlocale(locale.LC_ALL) == 'C'
+    assert system_locale != "C"
+    with setlocale("C"):
+        assert locale.setlocale(locale.LC_ALL) == "C"
     assert locale.setlocale(locale.LC_ALL) == system_locale
 
 
 def test_unsupported():
     system_locale = locale.setlocale(locale.LC_ALL)
     with pytest.raises(UnsupportedLocaleSettingError):
-        with setlocale('abcdef21'):
+        with setlocale("abcdef21"):
             pass
     assert locale.setlocale(locale.LC_ALL) == system_locale
 

+ 251 - 199
tests/test_surface.py

@@ -5,68 +5,78 @@ import numpy
 import pytest
 
 from conftest import ANNOTATION_FILE_PATH, SURFACE_FILE_PATH
-from freesurfer_surface import (Annotation, LineSegment, PolygonalCircuit,
-                                Surface, Triangle, Vertex, setlocale)
+from freesurfer_surface import (
+    Annotation,
+    LineSegment,
+    PolygonalCircuit,
+    Surface,
+    Triangle,
+    Vertex,
+    setlocale,
+)
 
 # pylint: disable=protected-access
 
 
 def test_read_triangular():
     surface = Surface.read_triangular(SURFACE_FILE_PATH)
-    assert surface.creator == b'fabianpeter'
-    assert surface.creation_datetime \
-        == datetime.datetime(2019, 5, 9, 22, 37, 41)
+    assert surface.creator == b"fabianpeter"
+    assert surface.creation_datetime == datetime.datetime(2019, 5, 9, 22, 37, 41)
     assert len(surface.vertices) == 155622
     assert len(surface.triangles) == 311240
     assert not surface.using_old_real_ras
     assert surface.volume_geometry_info == (
-        b'valid = 1  # volume info valid\n',
-        b'filename = ../mri/filled-pretess255.mgz\n',
-        b'volume = 256 256 256\n',
-        b'voxelsize = 1.000000000000000e+00 1.000000000000000e+00 1.000000000000000e+00\n',
-        b'xras   = -1.000000000000000e+00 0.000000000000000e+00 1.862645149230957e-09\n',
-        b'yras   = 0.000000000000000e+00 -6.655682227574289e-09 -1.000000000000000e+00\n',
-        b'zras   = 0.000000000000000e+00 1.000000000000000e+00 -8.300048648379743e-09\n',
-        b'cras   = -2.773597717285156e+00 1.566547393798828e+01 -7.504364013671875e+00\n')
+        b"valid = 1  # volume info valid\n",
+        b"filename = ../mri/filled-pretess255.mgz\n",
+        b"volume = 256 256 256\n",
+        b"voxelsize = 1.000000000000000e+00 1.000000000000000e+00 1.000000000000000e+00\n",
+        b"xras   = -1.000000000000000e+00 0.000000000000000e+00 1.862645149230957e-09\n",
+        b"yras   = 0.000000000000000e+00 -6.655682227574289e-09 -1.000000000000000e+00\n",
+        b"zras   = 0.000000000000000e+00 1.000000000000000e+00 -8.300048648379743e-09\n",
+        b"cras   = -2.773597717285156e+00 1.566547393798828e+01 -7.504364013671875e+00\n",
+    )
     assert surface.command_lines == [
-        b'mris_remove_intersection ../surf/lh.orig ../surf/lh.orig'
-        b' ProgramVersion: $Name: stable6 $'
-        b'  TimeStamp: 2019/05/09-17:42:36-GMT'
-        b'  BuildTimeStamp: Jan 18 2017 16:38:58'
-        b'  CVS: $Id: mris_remove_intersection.c,v 1.6 2011/03/02 00:04:32 nicks Exp $'
-        b'  User: fabianpeter'
-        b'  Machine: host12345'
-        b'  Platform: Linux'
-        b'  PlatformVersion: 4.15.0-46-generic'
-        b'  CompilerName: GCC'
-        b'  CompilerVersion: 40400'
-        b'  ',
-        b'mris_make_surfaces -orig_white white.preaparc -orig_pial white.preaparc'
-        b' -aseg ../mri/aseg.presurf -mgz -T1 brain.finalsurfs'
-        b' fabian20190509 lh ProgramVersion: $Name:  $'
-        b'  TimeStamp: 2019/05/09-20:27:28-GMT'
-        b'  BuildTimeStamp: Jan 18 2017 16:38:58'
-        b'  CVS: $Id: mris_make_surfaces.c,v 1.164.2.4 2016/12/13 22:26:32 zkaufman Exp $'
-        b'  User: fabianpeter'
-        b'  Machine: host12345'
-        b'  Platform: Linux'
-        b'  PlatformVersion: 4.15.0-46-generic'
-        b'  CompilerName: GCC'
-        b'  CompilerVersion: 40400'
-        b'  ']
+        b"mris_remove_intersection ../surf/lh.orig ../surf/lh.orig"
+        b" ProgramVersion: $Name: stable6 $"
+        b"  TimeStamp: 2019/05/09-17:42:36-GMT"
+        b"  BuildTimeStamp: Jan 18 2017 16:38:58"
+        b"  CVS: $Id: mris_remove_intersection.c,v 1.6 2011/03/02 00:04:32 nicks Exp $"
+        b"  User: fabianpeter"
+        b"  Machine: host12345"
+        b"  Platform: Linux"
+        b"  PlatformVersion: 4.15.0-46-generic"
+        b"  CompilerName: GCC"
+        b"  CompilerVersion: 40400"
+        b"  ",
+        b"mris_make_surfaces -orig_white white.preaparc -orig_pial white.preaparc"
+        b" -aseg ../mri/aseg.presurf -mgz -T1 brain.finalsurfs"
+        b" fabian20190509 lh ProgramVersion: $Name:  $"
+        b"  TimeStamp: 2019/05/09-20:27:28-GMT"
+        b"  BuildTimeStamp: Jan 18 2017 16:38:58"
+        b"  CVS: $Id: mris_make_surfaces.c,v 1.164.2.4 2016/12/13 22:26:32 zkaufman Exp $"
+        b"  User: fabianpeter"
+        b"  Machine: host12345"
+        b"  Platform: Linux"
+        b"  PlatformVersion: 4.15.0-46-generic"
+        b"  CompilerName: GCC"
+        b"  CompilerVersion: 40400"
+        b"  ",
+    ]
 
 
 def test_read_triangular_locale():
-    with setlocale('de_AT.utf8'):
+    with setlocale("de_AT.utf8"):
         surface = Surface.read_triangular(SURFACE_FILE_PATH)
-    assert surface.creation_datetime \
-        == datetime.datetime(2019, 5, 9, 22, 37, 41)
+    assert surface.creation_datetime == datetime.datetime(2019, 5, 9, 22, 37, 41)
 
 
-@pytest.mark.parametrize(('creation_datetime', 'expected_str'), [
-    (datetime.datetime(2019, 5, 9, 22, 37, 41), b'Thu May  9 22:37:41 2019'),
-    (datetime.datetime(2019, 4, 24, 23, 29, 22), b'Wed Apr 24 23:29:22 2019'),
-])
+@pytest.mark.parametrize(
+    ("creation_datetime", "expected_str"),
+    [
+        (datetime.datetime(2019, 5, 9, 22, 37, 41), b"Thu May  9 22:37:41 2019"),
+        (datetime.datetime(2019, 4, 24, 23, 29, 22), b"Wed Apr 24 23:29:22 2019"),
+    ],
+)
 def test_triangular_strftime(creation_datetime, expected_str):
     # pylint: disable=protected-access
     assert expected_str == Surface._triangular_strftime(creation_datetime)
@@ -74,65 +84,71 @@ def test_triangular_strftime(creation_datetime, expected_str):
 
 def test_read_write_triangular_same(tmpdir):
     surface = Surface.read_triangular(SURFACE_FILE_PATH)
-    output_path = tmpdir.join('surface').strpath
-    surface.write_triangular(output_path,
-                             creation_datetime=surface.creation_datetime)
-    with open(output_path, 'rb') as output_file:
-        with open(SURFACE_FILE_PATH, 'rb') as expected_file:
+    output_path = tmpdir.join("surface").strpath
+    surface.write_triangular(output_path, creation_datetime=surface.creation_datetime)
+    with open(output_path, "rb") as output_file:
+        with open(SURFACE_FILE_PATH, "rb") as expected_file:
             assert expected_file.read() == output_file.read()
 
 
 def test_read_write_datetime(tmpdir):
     surface = Surface.read_triangular(SURFACE_FILE_PATH)
     original_creation_datetime = surface.creation_datetime
-    output_path = tmpdir.join('surface').strpath
+    output_path = tmpdir.join("surface").strpath
     surface.write_triangular(output_path)
     assert original_creation_datetime == surface.creation_datetime
     new_surface = Surface.read_triangular(output_path)
     assert new_surface.creation_datetime > original_creation_datetime
     assert datetime.datetime.now() > new_surface.creation_datetime
-    assert (datetime.datetime.now() - new_surface.creation_datetime) \
-        < datetime.timedelta(seconds=20)
+    assert (
+        datetime.datetime.now() - new_surface.creation_datetime
+    ) < datetime.timedelta(seconds=20)
 
 
 def test_write_read_triangular_same(tmpdir):
     expected_surface = Surface()
-    expected_surface.creator = b'pytest'
+    expected_surface.creator = b"pytest"
     expected_surface.creation_datetime = datetime.datetime.now().replace(microsecond=0)
-    expected_surface.vertices = [Vertex(0.0, 0.0, 0.0),
-                                 Vertex(1.0, 2.0, 3.0),
-                                 Vertex(2.0, 4.0, 6.0),
-                                 Vertex(3.0, 5.0, 7.0)]
-    expected_surface.triangles = [Triangle((0, 1, 2)),
-                                  Triangle((0, 1, 3)),
-                                  Triangle((3, 2, 1))]
+    expected_surface.vertices = [
+        Vertex(0.0, 0.0, 0.0),
+        Vertex(1.0, 2.0, 3.0),
+        Vertex(2.0, 4.0, 6.0),
+        Vertex(3.0, 5.0, 7.0),
+    ]
+    expected_surface.triangles = [
+        Triangle((0, 1, 2)),
+        Triangle((0, 1, 3)),
+        Triangle((3, 2, 1)),
+    ]
     expected_surface.using_old_real_ras = False
-    expected_surface.volume_geometry_info = tuple(b'?\n' for _ in range(8))
-    expected_surface.command_lines = [b'?', b'!']
-    output_path = tmpdir.join('surface').strpath
-    expected_surface.write_triangular(output_path,
-                                      creation_datetime=expected_surface.creation_datetime)
+    expected_surface.volume_geometry_info = tuple(b"?\n" for _ in range(8))
+    expected_surface.command_lines = [b"?", b"!"]
+    output_path = tmpdir.join("surface").strpath
+    expected_surface.write_triangular(
+        output_path, creation_datetime=expected_surface.creation_datetime
+    )
     resulted_surface = Surface.read_triangular(output_path)
-    assert numpy.array_equal(expected_surface.vertices,
-                             resulted_surface.vertices)
+    assert numpy.array_equal(expected_surface.vertices, resulted_surface.vertices)
     expected_surface.vertices = resulted_surface.vertices = []
     assert vars(expected_surface) == vars(resulted_surface)
 
 
 def test_write_triangular_same_locale(tmpdir):
     surface = Surface()
-    surface.creator = b'pytest'
-    surface.volume_geometry_info = tuple(b'?' for _ in range(8))
+    surface.creator = b"pytest"
+    surface.volume_geometry_info = tuple(b"?" for _ in range(8))
     creation_datetime = datetime.datetime(2018, 12, 31, 21, 42)
-    output_path = tmpdir.join('surface').strpath
-    with setlocale('de_AT.utf8'):
-        surface.write_triangular(output_path,
-                                 creation_datetime=creation_datetime)
+    output_path = tmpdir.join("surface").strpath
+    with setlocale("de_AT.utf8"):
+        surface.write_triangular(output_path, creation_datetime=creation_datetime)
     resulted_surface = Surface.read_triangular(output_path)
     assert resulted_surface.creation_datetime == creation_datetime
-    with open(output_path, 'rb') as output_file:
-        assert output_file.read().split(b' on ')[1] \
-            .startswith(b'Mon Dec 31 21:42:00 2018\n')
+    with open(output_path, "rb") as output_file:
+        assert (
+            output_file.read()
+            .split(b" on ")[1]
+            .startswith(b"Mon Dec 31 21:42:00 2018\n")
+        )
 
 
 def test_load_annotation():
@@ -155,15 +171,18 @@ def test_add_vertex():
     assert surface.vertices[1].right == pytest.approx(-3.0)
 
 
-@pytest.mark.parametrize('vertices_coords', [
-    ((0, 0, 0), (2, 4, 0), (2, 4, 3)),
-    ((0, 0, 0), (2, 4, 0), (2, 4, 3), (0, 0, 3)),
-    ((1, 1, 0), (3, 5, 0), (3, 5, 3), (1, 1, 3)),
-    ((1, 1, 7), (3, 5, 7), (3, 5, 3), (1, 1, 3)),
-    ((1, 1, 1), (3, 5, 7), (3, 5, 9), (1, 1, 3)),
-    ((3, 5, 7), (1, 1, 1), (1, 1, 3)),
-    ((3, 5, 7), (1, 1, 1), (1, 1, 3), (3, 5, 9)),
-])
+@pytest.mark.parametrize(
+    "vertices_coords",
+    [
+        ((0, 0, 0), (2, 4, 0), (2, 4, 3)),
+        ((0, 0, 0), (2, 4, 0), (2, 4, 3), (0, 0, 3)),
+        ((1, 1, 0), (3, 5, 0), (3, 5, 3), (1, 1, 3)),
+        ((1, 1, 7), (3, 5, 7), (3, 5, 3), (1, 1, 3)),
+        ((1, 1, 1), (3, 5, 7), (3, 5, 9), (1, 1, 3)),
+        ((3, 5, 7), (1, 1, 1), (1, 1, 3)),
+        ((3, 5, 7), (1, 1, 1), (1, 1, 3), (3, 5, 9)),
+    ],
+)
 def test_add_rectangle(vertices_coords):
     surface = Surface()
     for vertex_coords in vertices_coords:
@@ -175,20 +194,22 @@ def test_add_rectangle(vertices_coords):
     assert surface.triangles[1].vertex_indices == (2, 3, 0)
 
 
-@pytest.mark.parametrize(('vertices_coords', 'expected_extra_vertex_coords'), [
-    (((0, 0, 0), (2, 4, 0), (2, 4, 3)), (0, 0, 3)),
-    (((1, 1, 0), (3, 5, 0), (3, 5, 3)), (1, 1, 3)),
-    (((1, 1, 7), (3, 5, 7), (3, 5, 3)), (1, 1, 3)),
-    (((1, 1, 1), (3, 5, 7), (3, 5, 9)), (1, 1, 3)),
-    (((3, 5, 7), (1, 1, 1), (1, 1, 3)), (3, 5, 9)),
-])
+@pytest.mark.parametrize(
+    ("vertices_coords", "expected_extra_vertex_coords"),
+    [
+        (((0, 0, 0), (2, 4, 0), (2, 4, 3)), (0, 0, 3)),
+        (((1, 1, 0), (3, 5, 0), (3, 5, 3)), (1, 1, 3)),
+        (((1, 1, 7), (3, 5, 7), (3, 5, 3)), (1, 1, 3)),
+        (((1, 1, 1), (3, 5, 7), (3, 5, 9)), (1, 1, 3)),
+        (((3, 5, 7), (1, 1, 1), (1, 1, 3)), (3, 5, 9)),
+    ],
+)
 def test_add_rectangle_3(vertices_coords, expected_extra_vertex_coords):
     surface = Surface()
     for vertex_coords in vertices_coords:
         surface.add_vertex(Vertex(*(float(c) for c in vertex_coords)))
     surface.add_rectangle(range(3))
-    assert tuple(surface.vertices[3]) \
-        == pytest.approx(expected_extra_vertex_coords)
+    assert tuple(surface.vertices[3]) == pytest.approx(expected_extra_vertex_coords)
 
 
 def test__triangle_count_by_adjacent_vertex_indices_empty():
@@ -201,18 +222,19 @@ def test__triangle_count_by_adjacent_vertex_indices_none():
     surface.vertices.append(Vertex(1, 0, 0))
     surface.vertices.append(Vertex(2, 0, 0))
     surface.vertices.append(Vertex(3, 0, 0))
-    assert surface._triangle_count_by_adjacent_vertex_indices() \
-        == {0: {}, 1: {}, 2: {}}
+    assert surface._triangle_count_by_adjacent_vertex_indices() == {0: {}, 1: {}, 2: {}}
 
 
 def test__triangle_count_by_adjacent_vertex_indices_single():
     surface = Surface()
-    surface.triangles.append(Triangle([surface.add_vertex(Vertex(i, 0, 0))
-                                       for i in range(3)]))
-    assert surface._triangle_count_by_adjacent_vertex_indices() \
-        == {0: {1: 1, 2: 1},
-            1: {0: 1, 2: 1},
-            2: {0: 1, 1: 1}}
+    surface.triangles.append(
+        Triangle([surface.add_vertex(Vertex(i, 0, 0)) for i in range(3)])
+    )
+    assert surface._triangle_count_by_adjacent_vertex_indices() == {
+        0: {1: 1, 2: 1},
+        1: {0: 1, 2: 1},
+        2: {0: 1, 1: 1},
+    }
 
 
 def test__triangle_count_by_adjacent_vertex_indices_multiple():
@@ -221,26 +243,29 @@ def test__triangle_count_by_adjacent_vertex_indices_multiple():
         surface.add_vertex(Vertex(i, 0, 0))
     surface.triangles.append(Triangle((0, 1, 2)))
     surface.triangles.append(Triangle((3, 1, 2)))
-    assert surface._triangle_count_by_adjacent_vertex_indices() \
-        == {0: {1: 1, 2: 1},
-            1: {0: 1, 2: 2, 3: 1},
-            2: {0: 1, 1: 2, 3: 1},
-            3: {1: 1, 2: 1},
-            4: {}}
+    assert surface._triangle_count_by_adjacent_vertex_indices() == {
+        0: {1: 1, 2: 1},
+        1: {0: 1, 2: 2, 3: 1},
+        2: {0: 1, 1: 2, 3: 1},
+        3: {1: 1, 2: 1},
+        4: {},
+    }
     surface.triangles.append(Triangle((3, 4, 2)))
-    assert surface._triangle_count_by_adjacent_vertex_indices() \
-        == {0: {1: 1, 2: 1},
-            1: {0: 1, 2: 2, 3: 1},
-            2: {0: 1, 1: 2, 3: 2, 4: 1},
-            3: {1: 1, 2: 2, 4: 1},
-            4: {2: 1, 3: 1}}
+    assert surface._triangle_count_by_adjacent_vertex_indices() == {
+        0: {1: 1, 2: 1},
+        1: {0: 1, 2: 2, 3: 1},
+        2: {0: 1, 1: 2, 3: 2, 4: 1},
+        3: {1: 1, 2: 2, 4: 1},
+        4: {2: 1, 3: 1},
+    }
     surface.triangles.append(Triangle((3, 0, 2)))
-    assert surface._triangle_count_by_adjacent_vertex_indices() \
-        == {0: {1: 1, 2: 2, 3: 1},
-            1: {0: 1, 2: 2, 3: 1},
-            2: {0: 2, 1: 2, 3: 3, 4: 1},
-            3: {0: 1, 1: 1, 2: 3, 4: 1},
-            4: {2: 1, 3: 1}}
+    assert surface._triangle_count_by_adjacent_vertex_indices() == {
+        0: {1: 1, 2: 2, 3: 1},
+        1: {0: 1, 2: 2, 3: 1},
+        2: {0: 2, 1: 2, 3: 3, 4: 1},
+        3: {0: 1, 1: 1, 2: 3, 4: 1},
+        4: {2: 1, 3: 1},
+    }
 
 
 def test__triangle_count_by_adjacent_vertex_indices_real():
@@ -248,12 +273,19 @@ def test__triangle_count_by_adjacent_vertex_indices_real():
     counts = surface._triangle_count_by_adjacent_vertex_indices()
     assert len(counts) == len(surface.vertices)
     assert all(counts.values())
-    assert all(count == 2
-               for vertex_counts in counts.values()
-               for count in vertex_counts.values())
-    assert sum(count for vertex_counts in counts.values()
-               for count in vertex_counts.values()) \
+    assert all(
+        count == 2
+        for vertex_counts in counts.values()
+        for count in vertex_counts.values()
+    )
+    assert (
+        sum(
+            count
+            for vertex_counts in counts.values()
+            for count in vertex_counts.values()
+        )
         == len(surface.triangles) * 6
+    )
 
 
 def test_find_borders_none():
@@ -271,8 +303,7 @@ def test_find_borders_single():
 
 def test_find_borders_singles():
     surface = Surface.read_triangular(SURFACE_FILE_PATH)
-    single_indices = [surface.add_vertex(Vertex(i, 21, 42))
-                      for i in range(3)]
+    single_indices = [surface.add_vertex(Vertex(i, 21, 42)) for i in range(3)]
     borders = set(surface.find_borders())
     assert len(borders) == 3
     assert PolygonalCircuit((single_indices[0],)) in borders
@@ -282,8 +313,7 @@ def test_find_borders_singles():
 
 def test_find_borders_single_triangle_simple():
     surface = Surface()
-    vertex_indices = [surface.add_vertex(Vertex(i, 21, 42))
-                      for i in range(3)]
+    vertex_indices = [surface.add_vertex(Vertex(i, 21, 42)) for i in range(3)]
     surface.triangles.append(Triangle(vertex_indices))
     borders = set(surface.find_borders())
     assert len(borders) == 1
@@ -292,8 +322,7 @@ def test_find_borders_single_triangle_simple():
 
 def test_find_borders_single_triangle_real():
     surface = Surface.read_triangular(SURFACE_FILE_PATH)
-    vertex_indices = [surface.add_vertex(Vertex(i, 21, 42))
-                      for i in range(3)]
+    vertex_indices = [surface.add_vertex(Vertex(i, 21, 42)) for i in range(3)]
     surface.triangles.append(Triangle(vertex_indices))
     borders = set(surface.find_borders())
     assert len(borders) == 1
@@ -310,8 +339,7 @@ def test_find_borders_remove_triangle():
 
 def test_find_borders_remove_non_adjacent_triangles():
     surface = Surface.read_triangular(SURFACE_FILE_PATH)
-    triangles = [surface.triangles.pop(),
-                 surface.triangles.pop()]
+    triangles = [surface.triangles.pop(), surface.triangles.pop()]
     borders = set(surface.find_borders())
     assert len(borders) == 2
     assert triangles[0] in borders
@@ -320,8 +348,7 @@ def test_find_borders_remove_non_adjacent_triangles():
 
 def test_find_borders_remove_adjacent_triangles():
     surface = Surface.read_triangular(SURFACE_FILE_PATH)
-    triangles = [surface.triangles.pop(),
-                 surface.triangles.pop()]
+    triangles = [surface.triangles.pop(), surface.triangles.pop()]
     triangles.append(surface.triangles.pop(270682))
     assert triangles[1] == Triangle((136141, 136142, 137076))
     assert triangles[2] == Triangle((136141, 136142, 135263))
@@ -333,45 +360,52 @@ def test_find_borders_remove_adjacent_triangles():
     borders = set(surface.find_borders())
     assert len(borders) == 2
     assert triangles[0] in borders
-    assert PolygonalCircuit((137076, 136141, 135263,
-                             135264, 136142)) in borders
+    assert PolygonalCircuit((137076, 136141, 135263, 135264, 136142)) in borders
     surface.triangles.pop(274320)
     borders = set(surface.find_borders())
     assert len(borders) == 2
     assert PolygonalCircuit((136143, 138007, 138008, 137078)) in borders
-    assert PolygonalCircuit((137076, 136141, 135263,
-                             135264, 136142)) in borders
-
-
-@pytest.mark.parametrize(('label_name', 'expected_border_lens'), [
-    ('precentral', [416]),
-    ('postcentral', [395]),
-    ('medialorbitofrontal', [6, 246]),
-    # ...--2343      2347
-    #          \    /    \
-    #           2345      2348
-    #          /    \    /
-    # ...--2344      2346
-    ('posteriorcingulate', [4, 190]),
-    ('unknown', [3, 390]),
-])
+    assert PolygonalCircuit((137076, 136141, 135263, 135264, 136142)) in borders
+
+
+@pytest.mark.parametrize(
+    ("label_name", "expected_border_lens"),
+    [
+        ("precentral", [416]),
+        ("postcentral", [395]),
+        ("medialorbitofrontal", [6, 246]),
+        # ...--2343      2347
+        #          \    /    \
+        #           2345      2348
+        #          /    \    /
+        # ...--2344      2346
+        ("posteriorcingulate", [4, 190]),
+        ("unknown", [3, 390]),
+    ],
+)
 def test_find_borders_real(label_name, expected_border_lens):
     surface = Surface.read_triangular(SURFACE_FILE_PATH)
     surface.load_annotation_file(ANNOTATION_FILE_PATH)
-    label, = filter(lambda l: l.name == label_name,
-                    surface.annotation.labels.values())
-    surface.triangles = list(filter(
-        lambda t: all(surface.annotation.vertex_label_index[vertex_idx]
-                      == label.index for vertex_idx in t.vertex_indices),
-        surface.triangles,
-    ))
+    (label,) = filter(
+        lambda l: l.name == label_name, surface.annotation.labels.values()
+    )
+    surface.triangles = list(
+        filter(
+            lambda t: all(
+                surface.annotation.vertex_label_index[vertex_idx] == label.index
+                for vertex_idx in t.vertex_indices
+            ),
+            surface.triangles,
+        )
+    )
     surface.remove_unused_vertices()
     borders = list(surface.find_borders())
     border_lens = [len(b.vertex_indices) for b in borders]
     # self-crossing borders may or may not be split into
     # separate polygonal circuits
-    assert sorted(border_lens) == expected_border_lens \
-        or sum(border_lens) == sum(expected_border_lens)
+    assert sorted(border_lens) == expected_border_lens or sum(border_lens) == sum(
+        expected_border_lens
+    )
 
 
 def test__get_vertex_label_index():
@@ -396,46 +430,57 @@ def test__get_vertex_label_index():
 def test__find_label_border_segments():
     surface = Surface.read_triangular(SURFACE_FILE_PATH)
     surface.load_annotation_file(ANNOTATION_FILE_PATH)
-    precentral_label, = filter(lambda l: l.name == 'precentral',
-                               surface.annotation.labels.values())
+    (precentral_label,) = filter(
+        lambda l: l.name == "precentral", surface.annotation.labels.values()
+    )
     # pylint: disable=protected-access
-    border_segments = set(
-        surface._find_label_border_segments(precentral_label))
+    border_segments = set(surface._find_label_border_segments(precentral_label))
     assert len(border_segments) == 417
     assert LineSegment((33450, 32065)) in border_segments
     assert LineSegment((33454, 33450)) in border_segments
     for border_vertex_index in [33450, 33454, 32065]:
-        assert surface.annotation.vertex_label_index[border_vertex_index] == precentral_label.index
+        assert (
+            surface.annotation.vertex_label_index[border_vertex_index]
+            == precentral_label.index
+        )
         for other_vertex_index in [32064, 33449, 33455, 33449, 33455]:
-            assert LineSegment((other_vertex_index, border_vertex_index)) \
+            assert (
+                LineSegment((other_vertex_index, border_vertex_index))
                 not in border_segments
-            assert LineSegment((border_vertex_index, other_vertex_index)) \
+            )
+            assert (
+                LineSegment((border_vertex_index, other_vertex_index))
                 not in border_segments
+            )
 
 
 def test__find_label_border_segments_incomplete_annotation():
     surface = Surface.read_triangular(SURFACE_FILE_PATH)
     surface.load_annotation_file(ANNOTATION_FILE_PATH)
-    precentral_label, = filter(lambda l: l.name == 'precentral',
-                               surface.annotation.labels.values())
+    (precentral_label,) = filter(
+        lambda l: l.name == "precentral", surface.annotation.labels.values()
+    )
     # pylint: disable=protected-access
     assert surface._find_label_border_segments(precentral_label)
-    surface.triangles.append(Triangle([
-        surface.add_vertex(Vertex(0.0, 21.0 * factor, 42.0 * factor))
-        for factor in range(3)
-    ]))
-    border_segments = set(
-        surface._find_label_border_segments(precentral_label))
+    surface.triangles.append(
+        Triangle(
+            [
+                surface.add_vertex(Vertex(0.0, 21.0 * factor, 42.0 * factor))
+                for factor in range(3)
+            ]
+        )
+    )
+    border_segments = set(surface._find_label_border_segments(precentral_label))
     assert len(border_segments) == 417
 
 
 def test_find_label_border_polygonal_chains():
     surface = Surface.read_triangular(SURFACE_FILE_PATH)
     surface.load_annotation_file(ANNOTATION_FILE_PATH)
-    precentral_label, = filter(lambda l: l.name == 'precentral',
-                               surface.annotation.labels.values())
-    border_chain, = surface.find_label_border_polygonal_chains(
-        precentral_label)
+    (precentral_label,) = filter(
+        lambda l: l.name == "precentral", surface.annotation.labels.values()
+    )
+    (border_chain,) = surface.find_label_border_polygonal_chains(precentral_label)
     vertex_indices = list(border_chain.vertex_indices)
     assert len(vertex_indices) == 418
     min_index = vertex_indices.index(min(vertex_indices))
@@ -461,8 +506,9 @@ def test__unused_vertices():
 def test__unused_vertices_real():
     surface = Surface.read_triangular(SURFACE_FILE_PATH)
     assert not surface._unused_vertices()
-    surface.triangles = list(filter(lambda t: 42 not in t.vertex_indices,
-                                    surface.triangles))
+    surface.triangles = list(
+        filter(lambda t: 42 not in t.vertex_indices, surface.triangles)
+    )
     assert surface._unused_vertices() == {42}
 
 
@@ -534,31 +580,36 @@ def test_remove_unused_vertices_single():
     assert len(surface.vertices) == 155622
     assert len(surface.triangles) == 311240
     assert surface.triangles[-1] == Triangle((136143, 138007, 137078))
-    surface.triangles = list(filter(lambda t: 42 not in t.vertex_indices,
-                                    surface.triangles))
+    surface.triangles = list(
+        filter(lambda t: 42 not in t.vertex_indices, surface.triangles)
+    )
     assert surface._unused_vertices() == {42}
     surface.remove_unused_vertices()
     assert len(surface.vertices) == 155622 - 1
     assert len(surface.triangles) == 311240 - 7
     assert surface.triangles[-1] == Triangle((136142, 138006, 137077))
-    assert all(vertex_index < len(surface.vertices)
-               for triangle in surface.triangles
-               for vertex_index in triangle.vertex_indices)
+    assert all(
+        vertex_index < len(surface.vertices)
+        for triangle in surface.triangles
+        for vertex_index in triangle.vertex_indices
+    )
 
 
 def test_select_vertices():
     surface = Surface()
     for i in range(4):
         surface.add_vertex(Vertex(i, i, i))
-    assert numpy.allclose(surface.select_vertices([2, 1]),
-                          [surface.vertices[2], surface.vertices[1]])
-    assert numpy.allclose(surface.select_vertices([3, 2]),
-                          [surface.vertices[3], surface.vertices[2]])
-    assert numpy.allclose(surface.select_vertices((3, 2)),
-                          [[3, 3, 3], [2, 2, 2]])
-    assert numpy.allclose(surface.select_vertices(filter(lambda i: i % 2 == 1,
-                                                         range(4))),
-                          [[1, 1, 1], [3, 3, 3]])
+    assert numpy.allclose(
+        surface.select_vertices([2, 1]), [surface.vertices[2], surface.vertices[1]]
+    )
+    assert numpy.allclose(
+        surface.select_vertices([3, 2]), [surface.vertices[3], surface.vertices[2]]
+    )
+    assert numpy.allclose(surface.select_vertices((3, 2)), [[3, 3, 3], [2, 2, 2]])
+    assert numpy.allclose(
+        surface.select_vertices(filter(lambda i: i % 2 == 1, range(4))),
+        [[1, 1, 1], [3, 3, 3]],
+    )
 
 
 def test_unite_2():
@@ -621,7 +672,8 @@ def test_unite_real():
     assert union.triangles[:-2] == surface_a.triangles
     assert union.triangles[-2:] == [
         Triangle((155622, 155623, 155625)),
-        Triangle((155623, 155625, 155626))]
+        Triangle((155623, 155625, 155626)),
+    ]
     assert union.creator == surface_a.creator
     assert union.creation_datetime == surface_a.creation_datetime
     assert union.using_old_real_ras == surface_a.using_old_real_ras

+ 1 - 1
tests/test_triangle.py

@@ -23,7 +23,7 @@ def test_eq():
 
 
 def test_repr():
-    assert repr(Triangle((0, 1, 2))) == 'Triangle(vertex_indices=(0, 1, 2))'
+    assert repr(Triangle((0, 1, 2))) == "Triangle(vertex_indices=(0, 1, 2))"
 
 
 def test_adjacent_vertex_indices_1():

+ 34 - 29
tests/test_vertex.py

@@ -21,12 +21,13 @@ def test_init_kwargs():
 
 def test_repr():
     vertex = Vertex(right=-4.0, superior=21.42, anterior=0.5)
-    assert repr(vertex) == 'Vertex(right=-4.0, anterior=0.5, superior=21.42)'
+    assert repr(vertex) == "Vertex(right=-4.0, anterior=0.5, superior=21.42)"
 
 
 def test_add():
-    assert Vertex(-1.5, 4, 2) + Vertex(2, -4.5, 3) \
-        == pytest.approx(Vertex(0.5, -0.5, 5))
+    assert Vertex(-1.5, 4, 2) + Vertex(2, -4.5, 3) == pytest.approx(
+        Vertex(0.5, -0.5, 5)
+    )
 
 
 def test_mult():
@@ -36,32 +37,36 @@ def test_mult():
 def test_vars():
     attrs = vars(Vertex(-1.5, 4, 2))
     assert len(attrs) == 3
-    assert attrs['right'] == pytest.approx(-1.5)
-    assert attrs['anterior'] == pytest.approx(4)
-    assert attrs['superior'] == pytest.approx(2)
+    assert attrs["right"] == pytest.approx(-1.5)
+    assert attrs["anterior"] == pytest.approx(4)
+    assert attrs["superior"] == pytest.approx(2)
 
 
-@pytest.mark.parametrize(('vertex_a', 'vertex_b', 'expected_distance_mm'), [
-    (Vertex(0, 0, 0), Vertex(0, 0, 0), 0),
-    (Vertex(0, 0, 0), Vertex(1, 0, 0), 1),
-    (Vertex(0, 0, 0), Vertex(0, 1, 0), 1),
-    (Vertex(0, 0, 0), Vertex(0, 0, 1), 1),
-    (Vertex(0, 0, 0), Vertex(1, 1, 0), 2**(1/2)),
-    (Vertex(0, 0, 0), Vertex(1, 1, 1), 3**(1/2)),
-    (Vertex(1, 2, 3), Vertex(2, 3, 4), 3**(1/2)),
-    (Vertex(1, 2, 3), Vertex(5, 8, -1), (16+36+16)**(1/2)),
-    (Vertex(0, 0, 0), [Vertex(0, 0, 1), Vertex(0, 0, 2)], [1, 2]),
-    (Vertex(0, 0, 0), (Vertex(0, 0, 1), Vertex(0, 0, 2)), [1, 2]),
-    (Vertex(0, 0, 0),
-     numpy.vstack((Vertex(0, 0, 1), Vertex(0, 0, 2))),
-     [1, 2]),
-    (Vertex(1, 2, 3),
-     (Vertex(2, 3, 4), Vertex(3, 4, 5)),
-     [3**(1/2), 12**(1/2)]),
-    (Vertex(1, 2, 3),
-     (Vertex(2, 3, 4), Vertex(3, 4, 5), Vertex(3, 4, 6)),
-     [3**(1/2), 12**(1/2), 17**(1/2)]),
-])
+@pytest.mark.parametrize(
+    ("vertex_a", "vertex_b", "expected_distance_mm"),
+    [
+        (Vertex(0, 0, 0), Vertex(0, 0, 0), 0),
+        (Vertex(0, 0, 0), Vertex(1, 0, 0), 1),
+        (Vertex(0, 0, 0), Vertex(0, 1, 0), 1),
+        (Vertex(0, 0, 0), Vertex(0, 0, 1), 1),
+        (Vertex(0, 0, 0), Vertex(1, 1, 0), 2 ** (1 / 2)),
+        (Vertex(0, 0, 0), Vertex(1, 1, 1), 3 ** (1 / 2)),
+        (Vertex(1, 2, 3), Vertex(2, 3, 4), 3 ** (1 / 2)),
+        (Vertex(1, 2, 3), Vertex(5, 8, -1), (16 + 36 + 16) ** (1 / 2)),
+        (Vertex(0, 0, 0), [Vertex(0, 0, 1), Vertex(0, 0, 2)], [1, 2]),
+        (Vertex(0, 0, 0), (Vertex(0, 0, 1), Vertex(0, 0, 2)), [1, 2]),
+        (Vertex(0, 0, 0), numpy.vstack((Vertex(0, 0, 1), Vertex(0, 0, 2))), [1, 2]),
+        (
+            Vertex(1, 2, 3),
+            (Vertex(2, 3, 4), Vertex(3, 4, 5)),
+            [3 ** (1 / 2), 12 ** (1 / 2)],
+        ),
+        (
+            Vertex(1, 2, 3),
+            (Vertex(2, 3, 4), Vertex(3, 4, 5), Vertex(3, 4, 6)),
+            [3 ** (1 / 2), 12 ** (1 / 2), 17 ** (1 / 2)],
+        ),
+    ],
+)
 def test_distance(vertex_a, vertex_b, expected_distance_mm):
-    assert vertex_a.distance_mm(vertex_b) \
-        == pytest.approx(expected_distance_mm)
+    assert vertex_a.distance_mm(vertex_b) == pytest.approx(expected_distance_mm)