From e787b9f98634db4145606bd25cbe717adc1b905f Mon Sep 17 00:00:00 2001 From: Hugh Sorby Date: Sat, 7 Dec 2024 12:53:08 +1300 Subject: [PATCH 01/18] Evaluating node range is failing on simple mesh. --- tests/resources/two_element_cube.exf | 342 +++++++++++++++++++++++++++ tests/test_fitcube.py | 21 +- 2 files changed, 362 insertions(+), 1 deletion(-) create mode 100644 tests/resources/two_element_cube.exf diff --git a/tests/resources/two_element_cube.exf b/tests/resources/two_element_cube.exf new file mode 100644 index 0000000..b94f189 --- /dev/null +++ b/tests/resources/two_element_cube.exf @@ -0,0 +1,342 @@ +EX Version: 3 +Region: / +!#nodeset nodes +Define node template: node1 +Shape. Dimension=0 +#Fields=1 +1) fitted coordinates, coordinate, rectangular cartesian, real, #Components=3 + x. #Values=4 (value,d/ds1,d/ds2,d/ds3) + y. #Values=4 (value,d/ds1,d/ds2,d/ds3) + z. #Values=4 (value,d/ds1,d/ds2,d/ds3) +Node template: node1 +Node: 1 + 0.000000000000000e+00 1.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 + 0.000000000000000e+00 0.000000000000000e+00 1.000000000000000e+00 0.000000000000000e+00 + 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 5.000000000000000e-01 +Node: 2 + 1.000000000000000e+00 9.999999999732445e-01 0.000000000000000e+00 0.000000000000000e+00 + 0.000000000000000e+00 0.000000000000000e+00 1.000000000000000e+00 0.000000000000000e+00 + 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 5.000000000000000e-01 +Node: 3 + 0.000000000000000e+00 1.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 + 1.000000000000000e+00 0.000000000000000e+00 9.999999999732445e-01 0.000000000000000e+00 + 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 5.000000000000000e-01 +Node: 4 + 1.000000000000000e+00 9.999999999732445e-01 0.000000000000000e+00 0.000000000000000e+00 + 1.000000000000000e+00 0.000000000000000e+00 9.999999999732445e-01 0.000000000000000e+00 + 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 5.000000000000000e-01 +Node: 5 + 0.000000000000000e+00 1.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 + 0.000000000000000e+00 0.000000000000000e+00 1.000000000000000e+00 0.000000000000000e+00 + 5.000000000000000e-01 0.000000000000000e+00 0.000000000000000e+00 5.000000000005000e-01 +Node: 6 + 1.000000000000000e+00 9.999999999732445e-01 0.000000000000000e+00 0.000000000000000e+00 + 0.000000000000000e+00 0.000000000000000e+00 1.000000000000000e+00 0.000000000000000e+00 + 5.000000000000000e-01 0.000000000000000e+00 0.000000000000000e+00 5.000000000005000e-01 +Node: 7 + 0.000000000000000e+00 1.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 + 1.000000000000000e+00 0.000000000000000e+00 9.999999999732445e-01 0.000000000000000e+00 + 5.000000000000000e-01 0.000000000000000e+00 0.000000000000000e+00 5.000000000005000e-01 +Node: 8 + 1.000000000000000e+00 9.999999999732445e-01 0.000000000000000e+00 0.000000000000000e+00 + 1.000000000000000e+00 0.000000000000000e+00 9.999999999732445e-01 0.000000000000000e+00 + 5.000000000000000e-01 0.000000000000000e+00 0.000000000000000e+00 5.000000000005000e-01 +Node: 9 + 0.000000000000000e+00 1.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 + 0.000000000000000e+00 0.000000000000000e+00 1.000000000000000e+00 0.000000000000000e+00 + 1.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 4.999999999866223e-01 +Node: 10 + 1.000000000000000e+00 9.999999999732445e-01 0.000000000000000e+00 0.000000000000000e+00 + 0.000000000000000e+00 0.000000000000000e+00 1.000000000000000e+00 0.000000000000000e+00 + 1.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 4.999999999866223e-01 +Node: 11 + 0.000000000000000e+00 1.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 + 1.000000000000000e+00 0.000000000000000e+00 9.999999999732445e-01 0.000000000000000e+00 + 1.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 4.999999999866223e-01 +Node: 12 + 1.000000000000000e+00 9.999999999732445e-01 0.000000000000000e+00 0.000000000000000e+00 + 1.000000000000000e+00 0.000000000000000e+00 9.999999999732445e-01 0.000000000000000e+00 + 1.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 4.999999999866223e-01 +!#mesh mesh1d, dimension=1, nodeset=nodes +Define element template: element1 +Shape. Dimension=1, line +#Scale factor sets=0 +#Nodes=0 +#Fields=0 +Element template: element1 +Element: 1 +Element: 2 +Element: 3 +Element: 4 +Element: 5 +Element: 6 +Element: 7 +Element: 8 +Element: 9 +Element: 10 +Element: 11 +Element: 12 +Element: 13 +Element: 14 +Element: 15 +Element: 16 +Element: 17 +Element: 18 +Element: 19 +Element: 20 +!#mesh mesh2d, dimension=2, face mesh=mesh1d, nodeset=nodes +Define element template: element2 +Shape. Dimension=2, line*line +#Scale factor sets=0 +#Nodes=0 +#Fields=0 +Element template: element2 +Element: 1 + Faces: + 1 2 3 4 +Element: 2 + Faces: + 5 6 7 8 +Element: 3 + Faces: + 9 10 1 5 +Element: 4 + Faces: + 11 12 2 6 +Element: 5 + Faces: + 3 7 9 11 +Element: 6 + Faces: + 4 8 10 12 +Element: 7 + Faces: + 13 14 4 15 +Element: 8 + Faces: + 16 17 8 18 +Element: 9 + Faces: + 10 19 13 16 +Element: 10 + Faces: + 12 20 14 17 +Element: 11 + Faces: + 15 18 19 20 +!#mesh mesh3d, dimension=3, face mesh=mesh2d, nodeset=nodes +Define element template: element3 +Shape. Dimension=3, line*line*line +#Scale factor sets=0 +#Nodes=8 +#Fields=1 +1) fitted coordinates, coordinate, rectangular cartesian, real, #Components=3 + x. c.Hermite*c.Hermite*c.Hermite, no modify, standard node based. + #Nodes=8 + 1. #Values=3 + Value labels: value d/ds1 d/ds2 + 0. #Values=1 + Value labels: zero + 1. #Values=1 + Value labels: d/ds3 + 0. #Values=3 + Value labels: zero zero zero + 2. #Values=3 + Value labels: value d/ds1 d/ds2 + 0. #Values=1 + Value labels: zero + 2. #Values=1 + Value labels: d/ds3 + 0. #Values=3 + Value labels: zero zero zero + 3. #Values=3 + Value labels: value d/ds1 d/ds2 + 0. #Values=1 + Value labels: zero + 3. #Values=1 + Value labels: d/ds3 + 0. #Values=3 + Value labels: zero zero zero + 4. #Values=3 + Value labels: value d/ds1 d/ds2 + 0. #Values=1 + Value labels: zero + 4. #Values=1 + Value labels: d/ds3 + 0. #Values=3 + Value labels: zero zero zero + 5. #Values=3 + Value labels: value d/ds1 d/ds2 + 0. #Values=1 + Value labels: zero + 5. #Values=1 + Value labels: d/ds3 + 0. #Values=3 + Value labels: zero zero zero + 6. #Values=3 + Value labels: value d/ds1 d/ds2 + 0. #Values=1 + Value labels: zero + 6. #Values=1 + Value labels: d/ds3 + 0. #Values=3 + Value labels: zero zero zero + 7. #Values=3 + Value labels: value d/ds1 d/ds2 + 0. #Values=1 + Value labels: zero + 7. #Values=1 + Value labels: d/ds3 + 0. #Values=3 + Value labels: zero zero zero + 8. #Values=3 + Value labels: value d/ds1 d/ds2 + 0. #Values=1 + Value labels: zero + 8. #Values=1 + Value labels: d/ds3 + 0. #Values=3 + Value labels: zero zero zero + y. c.Hermite*c.Hermite*c.Hermite, no modify, standard node based. + #Nodes=8 + 1. #Values=3 + Value labels: value d/ds1 d/ds2 + 0. #Values=1 + Value labels: zero + 1. #Values=1 + Value labels: d/ds3 + 0. #Values=3 + Value labels: zero zero zero + 2. #Values=3 + Value labels: value d/ds1 d/ds2 + 0. #Values=1 + Value labels: zero + 2. #Values=1 + Value labels: d/ds3 + 0. #Values=3 + Value labels: zero zero zero + 3. #Values=3 + Value labels: value d/ds1 d/ds2 + 0. #Values=1 + Value labels: zero + 3. #Values=1 + Value labels: d/ds3 + 0. #Values=3 + Value labels: zero zero zero + 4. #Values=3 + Value labels: value d/ds1 d/ds2 + 0. #Values=1 + Value labels: zero + 4. #Values=1 + Value labels: d/ds3 + 0. #Values=3 + Value labels: zero zero zero + 5. #Values=3 + Value labels: value d/ds1 d/ds2 + 0. #Values=1 + Value labels: zero + 5. #Values=1 + Value labels: d/ds3 + 0. #Values=3 + Value labels: zero zero zero + 6. #Values=3 + Value labels: value d/ds1 d/ds2 + 0. #Values=1 + Value labels: zero + 6. #Values=1 + Value labels: d/ds3 + 0. #Values=3 + Value labels: zero zero zero + 7. #Values=3 + Value labels: value d/ds1 d/ds2 + 0. #Values=1 + Value labels: zero + 7. #Values=1 + Value labels: d/ds3 + 0. #Values=3 + Value labels: zero zero zero + 8. #Values=3 + Value labels: value d/ds1 d/ds2 + 0. #Values=1 + Value labels: zero + 8. #Values=1 + Value labels: d/ds3 + 0. #Values=3 + Value labels: zero zero zero + z. c.Hermite*c.Hermite*c.Hermite, no modify, standard node based. + #Nodes=8 + 1. #Values=3 + Value labels: value d/ds1 d/ds2 + 0. #Values=1 + Value labels: zero + 1. #Values=1 + Value labels: d/ds3 + 0. #Values=3 + Value labels: zero zero zero + 2. #Values=3 + Value labels: value d/ds1 d/ds2 + 0. #Values=1 + Value labels: zero + 2. #Values=1 + Value labels: d/ds3 + 0. #Values=3 + Value labels: zero zero zero + 3. #Values=3 + Value labels: value d/ds1 d/ds2 + 0. #Values=1 + Value labels: zero + 3. #Values=1 + Value labels: d/ds3 + 0. #Values=3 + Value labels: zero zero zero + 4. #Values=3 + Value labels: value d/ds1 d/ds2 + 0. #Values=1 + Value labels: zero + 4. #Values=1 + Value labels: d/ds3 + 0. #Values=3 + Value labels: zero zero zero + 5. #Values=3 + Value labels: value d/ds1 d/ds2 + 0. #Values=1 + Value labels: zero + 5. #Values=1 + Value labels: d/ds3 + 0. #Values=3 + Value labels: zero zero zero + 6. #Values=3 + Value labels: value d/ds1 d/ds2 + 0. #Values=1 + Value labels: zero + 6. #Values=1 + Value labels: d/ds3 + 0. #Values=3 + Value labels: zero zero zero + 7. #Values=3 + Value labels: value d/ds1 d/ds2 + 0. #Values=1 + Value labels: zero + 7. #Values=1 + Value labels: d/ds3 + 0. #Values=3 + Value labels: zero zero zero + 8. #Values=3 + Value labels: value d/ds1 d/ds2 + 0. #Values=1 + Value labels: zero + 8. #Values=1 + Value labels: d/ds3 + 0. #Values=3 + Value labels: zero zero zero +Element template: element3 +Element: 1 + Faces: + 1 2 3 4 5 6 + Nodes: + 1 2 3 4 5 6 7 8 +Element: 2 + Faces: + 7 8 9 10 6 11 + Nodes: + 5 6 7 8 9 10 11 12 diff --git a/tests/test_fitcube.py b/tests/test_fitcube.py index 23b9221..d97beb9 100644 --- a/tests/test_fitcube.py +++ b/tests/test_fitcube.py @@ -2,7 +2,8 @@ import os import unittest from cmlibs.utils.zinc.field import createFieldMeshIntegral -from cmlibs.utils.zinc.finiteelement import evaluate_field_nodeset_mean, find_node_with_name +from cmlibs.utils.zinc.finiteelement import evaluate_field_nodeset_mean, find_node_with_name, evaluate_field_nodeset_range +from cmlibs.zinc.context import Context from cmlibs.zinc.field import Field from cmlibs.zinc.node import Node, Nodeset from cmlibs.zinc.result import RESULT_OK @@ -765,6 +766,24 @@ def test_modelFitGroupMarkers(self): self.assertAlmostEqual(expectedLocation[1][1], xi[1], delta=TOL) self.assertAlmostEqual(expectedLocation[1][2], xi[2], delta=TOL) + def test_nodeset_max_and_min(self): + zinc_model_file = os.path.join(here, "resources", "two_element_cube.exf") + + context = Context("max_min") + logger = context.getLogger() + region = context.getDefaultRegion() + region.readFile(zinc_model_file) + + fm = region.getFieldmodule() + + nodes = fm.findNodesetByFieldDomainType(Field.DOMAIN_TYPE_NODES) + coordinates = fm.findFieldByName("coordinates") + self.assertEqual(0, logger.getNumberOfMessages()) + min_range, max_range = evaluate_field_nodeset_range(coordinates, nodes) + self.assertEqual(0, logger.getNumberOfMessages()) + self.assertIsNotNone(min_range) + self.assertIsNotNone(max_range) + if __name__ == "__main__": unittest.main() From ac9a9be9d5f88fc01208be2df758251250ee0b37 Mon Sep 17 00:00:00 2001 From: Hugh Sorby Date: Sat, 7 Dec 2024 13:03:01 +1300 Subject: [PATCH 02/18] Add GitHub action to run tests on PR. --- .github/workflows/run_tests.yaml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .github/workflows/run_tests.yaml diff --git a/.github/workflows/run_tests.yaml b/.github/workflows/run_tests.yaml new file mode 100644 index 0000000..ed619ef --- /dev/null +++ b/.github/workflows/run_tests.yaml @@ -0,0 +1,25 @@ +name: Build and test library + +on: + pull_request: + +jobs: + build-and-test: + runs-on: ubuntu-20.04 + name: Run tests + steps: + - name: Clone source + uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: 3.12 + - name: Install library + shell: bash + run: | + pip install scaffoldfitter + - name: Run tests + shell: bash + run: | + cd scaffoldfitter + python -m unittest --discover -s From 9c649332915bddbe0c7ebedb82ea8229fc82c403 Mon Sep 17 00:00:00 2001 From: Hugh Sorby Date: Sat, 7 Dec 2024 13:07:09 +1300 Subject: [PATCH 03/18] Install this version of scaffoldfitter not PyPI version. --- .github/workflows/run_tests.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/run_tests.yaml b/.github/workflows/run_tests.yaml index ed619ef..2a8bf4c 100644 --- a/.github/workflows/run_tests.yaml +++ b/.github/workflows/run_tests.yaml @@ -1,4 +1,4 @@ -name: Build and test library +name: Test library on: pull_request: @@ -17,9 +17,9 @@ jobs: - name: Install library shell: bash run: | - pip install scaffoldfitter + ls + pip install . - name: Run tests shell: bash run: | - cd scaffoldfitter python -m unittest --discover -s From 830e0b6738301892a9d0e80944fffd0cbc70b49e Mon Sep 17 00:00:00 2001 From: Hugh Sorby Date: Sat, 7 Dec 2024 13:10:38 +1300 Subject: [PATCH 04/18] Set version of cmlibs.zinc rquired to 4.*. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index d364845..f36de83 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ def readfile(filename, split=False): # minimal requirements listing "cmlibs.maths >= 0.3", "cmlibs.utils >= 0.10", - "cmlibs.zinc >= 4.0" + "cmlibs.zinc >= 4.*" ] readme.extend(['', 'License', '=======', '', '::', '']) source_license = readfile("LICENSE") From 04a063e6de096b9da89bd8c4908d7a1170e5a416 Mon Sep 17 00:00:00 2001 From: Hugh Sorby Date: Sat, 7 Dec 2024 13:12:07 +1300 Subject: [PATCH 05/18] Set version of cmlibs.zinc required to 4. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f36de83..00400c5 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ def readfile(filename, split=False): # minimal requirements listing "cmlibs.maths >= 0.3", "cmlibs.utils >= 0.10", - "cmlibs.zinc >= 4.*" + "cmlibs.zinc >= 4" ] readme.extend(['', 'License', '=======', '', '::', '']) source_license = readfile("LICENSE") From 9f434c55bb900394f6db6944492e612374ba1aac Mon Sep 17 00:00:00 2001 From: Hugh Sorby Date: Sat, 7 Dec 2024 13:16:23 +1300 Subject: [PATCH 06/18] Use macos-13 for running CI testing. --- .github/workflows/run_tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run_tests.yaml b/.github/workflows/run_tests.yaml index 2a8bf4c..d0fc3b3 100644 --- a/.github/workflows/run_tests.yaml +++ b/.github/workflows/run_tests.yaml @@ -5,7 +5,7 @@ on: jobs: build-and-test: - runs-on: ubuntu-20.04 + runs-on: macos-14 name: Run tests steps: - name: Clone source From 954490d6266d2b9372277a745a0897e34309ad61 Mon Sep 17 00:00:00 2001 From: Hugh Sorby Date: Sat, 7 Dec 2024 13:18:27 +1300 Subject: [PATCH 07/18] Use Python 3.11 for CI testing. --- .github/workflows/run_tests.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/run_tests.yaml b/.github/workflows/run_tests.yaml index d0fc3b3..d00aa02 100644 --- a/.github/workflows/run_tests.yaml +++ b/.github/workflows/run_tests.yaml @@ -13,11 +13,10 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: 3.12 + python-version: 3.11 - name: Install library shell: bash run: | - ls pip install . - name: Run tests shell: bash From aa6cd868d9805fdb6c77bbca3ae20b6c25292363 Mon Sep 17 00:00:00 2001 From: Hugh Sorby Date: Sat, 7 Dec 2024 13:24:14 +1300 Subject: [PATCH 08/18] Install linux wheel directly from Zinc release. --- .github/workflows/run_tests.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/run_tests.yaml b/.github/workflows/run_tests.yaml index d00aa02..d0fe1f8 100644 --- a/.github/workflows/run_tests.yaml +++ b/.github/workflows/run_tests.yaml @@ -5,7 +5,7 @@ on: jobs: build-and-test: - runs-on: macos-14 + runs-on: ubuntu-22.04 name: Run tests steps: - name: Clone source @@ -17,6 +17,7 @@ jobs: - name: Install library shell: bash run: | + pip install https://github.com/cmlibs/zinc/releases/download/v4.2.0/cmlibs.zinc-4.2.0-cp311-cp311-linux_x86_64.whl pip install . - name: Run tests shell: bash From 61e35973cfab003065edd35a92441fee566fa113 Mon Sep 17 00:00:00 2001 From: Hugh Sorby Date: Sat, 7 Dec 2024 13:33:37 +1300 Subject: [PATCH 09/18] Correct unittest test command in CI test. --- .github/workflows/run_tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run_tests.yaml b/.github/workflows/run_tests.yaml index d0fe1f8..4abdf70 100644 --- a/.github/workflows/run_tests.yaml +++ b/.github/workflows/run_tests.yaml @@ -22,4 +22,4 @@ jobs: - name: Run tests shell: bash run: | - python -m unittest --discover -s + python -m unittest tests From 60fa064cd3cabeac6f02e2716cc8d1f30cc57b7d Mon Sep 17 00:00:00 2001 From: Hugh Sorby Date: Sat, 7 Dec 2024 13:36:55 +1300 Subject: [PATCH 10/18] Correct unittest test command in CI test. --- .github/workflows/run_tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run_tests.yaml b/.github/workflows/run_tests.yaml index 4abdf70..f7a3e78 100644 --- a/.github/workflows/run_tests.yaml +++ b/.github/workflows/run_tests.yaml @@ -22,4 +22,4 @@ jobs: - name: Run tests shell: bash run: | - python -m unittest tests + python -m unittest discover -s tests/ From 1a6fa084d5de1ef75d2d4b6c452bd5e6d0cf52fa Mon Sep 17 00:00:00 2001 From: Hugh Sorby Date: Sat, 7 Dec 2024 13:39:46 +1300 Subject: [PATCH 11/18] Install OpenGL for CI tests. --- .github/workflows/run_tests.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/run_tests.yaml b/.github/workflows/run_tests.yaml index f7a3e78..4bec581 100644 --- a/.github/workflows/run_tests.yaml +++ b/.github/workflows/run_tests.yaml @@ -14,6 +14,9 @@ jobs: uses: actions/setup-python@v5 with: python-version: 3.11 + - name: Install dependencies + run: | + sudo apt install libopengl0 libglu1-mesa -y - name: Install library shell: bash run: | From 9eb8b1b6122f42a17934bff82ea0b694843d88f1 Mon Sep 17 00:00:00 2001 From: Hugh Sorby Date: Sun, 8 Dec 2024 17:56:15 +1300 Subject: [PATCH 12/18] Replace two element cube with proper mesh file. --- tests/resources/two_element_cube.exf | 75 ++++++++++++++++++++-------- tests/test_fitcube.py | 7 ++- 2 files changed, 59 insertions(+), 23 deletions(-) diff --git a/tests/resources/two_element_cube.exf b/tests/resources/two_element_cube.exf index b94f189..f5b01f6 100644 --- a/tests/resources/two_element_cube.exf +++ b/tests/resources/two_element_cube.exf @@ -4,7 +4,7 @@ Region: / Define node template: node1 Shape. Dimension=0 #Fields=1 -1) fitted coordinates, coordinate, rectangular cartesian, real, #Components=3 +1) coordinates, coordinate, rectangular cartesian, real, #Components=3 x. #Values=4 (value,d/ds1,d/ds2,d/ds3) y. #Values=4 (value,d/ds1,d/ds2,d/ds3) z. #Values=4 (value,d/ds1,d/ds2,d/ds3) @@ -14,49 +14,49 @@ Node: 1 0.000000000000000e+00 0.000000000000000e+00 1.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 5.000000000000000e-01 Node: 2 - 1.000000000000000e+00 9.999999999732445e-01 0.000000000000000e+00 0.000000000000000e+00 + 1.000000000000000e+00 1.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 1.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 5.000000000000000e-01 Node: 3 0.000000000000000e+00 1.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 - 1.000000000000000e+00 0.000000000000000e+00 9.999999999732445e-01 0.000000000000000e+00 + 1.000000000000000e+00 0.000000000000000e+00 1.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 5.000000000000000e-01 Node: 4 - 1.000000000000000e+00 9.999999999732445e-01 0.000000000000000e+00 0.000000000000000e+00 - 1.000000000000000e+00 0.000000000000000e+00 9.999999999732445e-01 0.000000000000000e+00 + 1.000000000000000e+00 1.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 + 1.000000000000000e+00 0.000000000000000e+00 1.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 5.000000000000000e-01 Node: 5 0.000000000000000e+00 1.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 1.000000000000000e+00 0.000000000000000e+00 - 5.000000000000000e-01 0.000000000000000e+00 0.000000000000000e+00 5.000000000005000e-01 + 5.000000000000000e-01 0.000000000000000e+00 0.000000000000000e+00 5.000000000000000e-01 Node: 6 - 1.000000000000000e+00 9.999999999732445e-01 0.000000000000000e+00 0.000000000000000e+00 + 1.000000000000000e+00 1.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 1.000000000000000e+00 0.000000000000000e+00 - 5.000000000000000e-01 0.000000000000000e+00 0.000000000000000e+00 5.000000000005000e-01 + 5.000000000000000e-01 0.000000000000000e+00 0.000000000000000e+00 5.000000000000000e-01 Node: 7 0.000000000000000e+00 1.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 - 1.000000000000000e+00 0.000000000000000e+00 9.999999999732445e-01 0.000000000000000e+00 - 5.000000000000000e-01 0.000000000000000e+00 0.000000000000000e+00 5.000000000005000e-01 + 1.000000000000000e+00 0.000000000000000e+00 1.000000000000000e+00 0.000000000000000e+00 + 5.000000000000000e-01 0.000000000000000e+00 0.000000000000000e+00 5.000000000000000e-01 Node: 8 - 1.000000000000000e+00 9.999999999732445e-01 0.000000000000000e+00 0.000000000000000e+00 - 1.000000000000000e+00 0.000000000000000e+00 9.999999999732445e-01 0.000000000000000e+00 - 5.000000000000000e-01 0.000000000000000e+00 0.000000000000000e+00 5.000000000005000e-01 + 1.000000000000000e+00 1.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 + 1.000000000000000e+00 0.000000000000000e+00 1.000000000000000e+00 0.000000000000000e+00 + 5.000000000000000e-01 0.000000000000000e+00 0.000000000000000e+00 5.000000000000000e-01 Node: 9 0.000000000000000e+00 1.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 1.000000000000000e+00 0.000000000000000e+00 - 1.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 4.999999999866223e-01 + 1.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 5.000000000000000e-01 Node: 10 - 1.000000000000000e+00 9.999999999732445e-01 0.000000000000000e+00 0.000000000000000e+00 + 1.000000000000000e+00 1.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 1.000000000000000e+00 0.000000000000000e+00 - 1.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 4.999999999866223e-01 + 1.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 5.000000000000000e-01 Node: 11 0.000000000000000e+00 1.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 - 1.000000000000000e+00 0.000000000000000e+00 9.999999999732445e-01 0.000000000000000e+00 - 1.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 4.999999999866223e-01 + 1.000000000000000e+00 0.000000000000000e+00 1.000000000000000e+00 0.000000000000000e+00 + 1.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 5.000000000000000e-01 Node: 12 - 1.000000000000000e+00 9.999999999732445e-01 0.000000000000000e+00 0.000000000000000e+00 - 1.000000000000000e+00 0.000000000000000e+00 9.999999999732445e-01 0.000000000000000e+00 - 1.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 4.999999999866223e-01 + 1.000000000000000e+00 1.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 + 1.000000000000000e+00 0.000000000000000e+00 1.000000000000000e+00 0.000000000000000e+00 + 1.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 5.000000000000000e-01 !#mesh mesh1d, dimension=1, nodeset=nodes Define element template: element1 Shape. Dimension=1, line @@ -130,7 +130,7 @@ Shape. Dimension=3, line*line*line #Scale factor sets=0 #Nodes=8 #Fields=1 -1) fitted coordinates, coordinate, rectangular cartesian, real, #Components=3 +1) coordinates, coordinate, rectangular cartesian, real, #Components=3 x. c.Hermite*c.Hermite*c.Hermite, no modify, standard node based. #Nodes=8 1. #Values=3 @@ -340,3 +340,34 @@ Element: 2 7 8 9 10 6 11 Nodes: 5 6 7 8 9 10 11 12 +Group name: bottom +!#nodeset nodes +Node group: +1..4 +!#mesh mesh1d, dimension=1, nodeset=nodes +Element group: +3,7,9,11 +!#mesh mesh2d, dimension=2, face mesh=mesh1d, nodeset=nodes +Element group: +5 +Group name: marker +Group name: sides +!#nodeset nodes +Node group: +1..12 +!#mesh mesh1d, dimension=1, nodeset=nodes +Element group: +1..20 +!#mesh mesh2d, dimension=2, face mesh=mesh1d, nodeset=nodes +Element group: +1..4,7..10 +Group name: top +!#nodeset nodes +Node group: +9..12 +!#mesh mesh1d, dimension=1, nodeset=nodes +Element group: +15,18..20 +!#mesh mesh2d, dimension=2, face mesh=mesh1d, nodeset=nodes +Element group: +11 diff --git a/tests/test_fitcube.py b/tests/test_fitcube.py index d97beb9..45b9907 100644 --- a/tests/test_fitcube.py +++ b/tests/test_fitcube.py @@ -775,11 +775,16 @@ def test_nodeset_max_and_min(self): region.readFile(zinc_model_file) fm = region.getFieldmodule() + print(fm.isValid()) - nodes = fm.findNodesetByFieldDomainType(Field.DOMAIN_TYPE_NODES) + nodes = fm.findNodesetByFieldDomainType(Field.DOMAIN_TYPE_DATAPOINTS) coordinates = fm.findFieldByName("coordinates") + print(coordinates.isValid()) self.assertEqual(0, logger.getNumberOfMessages()) min_range, max_range = evaluate_field_nodeset_range(coordinates, nodes) + print("------ ", logger.getNumberOfMessages()) + for i in range(1, logger.getNumberOfMessages() + 1): + print(logger.getMessageTypeAtIndex(i), logger.getMessageTextAtIndex(i)) self.assertEqual(0, logger.getNumberOfMessages()) self.assertIsNotNone(min_range) self.assertIsNotNone(max_range) From 6aadf273b194c8c45fe613c9fdeb246ed0c5c44b Mon Sep 17 00:00:00 2001 From: Hugh Sorby Date: Thu, 12 Dec 2024 11:44:25 +1300 Subject: [PATCH 13/18] Add test for checking expected responses for nodeset range values. --- tests/test_fitcube.py | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/tests/test_fitcube.py b/tests/test_fitcube.py index 45b9907..f5593cd 100644 --- a/tests/test_fitcube.py +++ b/tests/test_fitcube.py @@ -3,6 +3,7 @@ import unittest from cmlibs.utils.zinc.field import createFieldMeshIntegral from cmlibs.utils.zinc.finiteelement import evaluate_field_nodeset_mean, find_node_with_name, evaluate_field_nodeset_range +from cmlibs.utils.zinc.region import write_to_buffer, read_from_buffer from cmlibs.zinc.context import Context from cmlibs.zinc.field import Field from cmlibs.zinc.node import Node, Nodeset @@ -773,22 +774,33 @@ def test_nodeset_max_and_min(self): logger = context.getLogger() region = context.getDefaultRegion() region.readFile(zinc_model_file) + data_region = context.createRegion() + data_region.readFile(os.path.join(here, "resources", "cube_to_sphere_data_random.exf")) fm = region.getFieldmodule() - print(fm.isValid()) + buffer = write_to_buffer(data_region, resource_domain_type=Field.DOMAIN_TYPE_DATAPOINTS) + result = read_from_buffer(region, buffer) nodes = fm.findNodesetByFieldDomainType(Field.DOMAIN_TYPE_DATAPOINTS) - coordinates = fm.findFieldByName("coordinates") - print(coordinates.isValid()) + + # Coordinate field 'data_coordinates' is defined on datapoints so nodeset evaluation should + # result in something that is not None being returned. + data_coordinates = fm.findFieldByName("data_coordinates") self.assertEqual(0, logger.getNumberOfMessages()) - min_range, max_range = evaluate_field_nodeset_range(coordinates, nodes) - print("------ ", logger.getNumberOfMessages()) - for i in range(1, logger.getNumberOfMessages() + 1): - print(logger.getMessageTypeAtIndex(i), logger.getMessageTextAtIndex(i)) + min_range, max_range = evaluate_field_nodeset_range(data_coordinates, nodes) self.assertEqual(0, logger.getNumberOfMessages()) self.assertIsNotNone(min_range) self.assertIsNotNone(max_range) + # Coordinate field 'coordinates' is not defined on datapoints so nodeset evaluation should + # result in None being returned. + coordinates = fm.findFieldByName("coordinates") + self.assertEqual(0, logger.getNumberOfMessages()) + min_range, max_range = evaluate_field_nodeset_range(coordinates, nodes) + self.assertEqual(0, logger.getNumberOfMessages()) + self.assertIsNone(min_range) + self.assertIsNone(max_range) + if __name__ == "__main__": unittest.main() From a7e80617f5ff76cc59a47c882b52d84b304af4a9 Mon Sep 17 00:00:00 2001 From: Hugh Sorby Date: Thu, 12 Dec 2024 11:47:32 +1300 Subject: [PATCH 14/18] Add concept of manual alignment mode. Add API for whether group and marker alignment can be performed. --- src/scaffoldfitter/fitter.py | 7 ++-- src/scaffoldfitter/fitterstepalign.py | 47 ++++++++++++++++++++++----- 2 files changed, 43 insertions(+), 11 deletions(-) diff --git a/src/scaffoldfitter/fitter.py b/src/scaffoldfitter/fitter.py index 2537a39..58d1410 100644 --- a/src/scaffoldfitter/fitter.py +++ b/src/scaffoldfitter/fitter.py @@ -283,12 +283,13 @@ def load(self): """ self._clearFields() self._region = self._context.createRegion() + self._region.setName("model_region") self._fieldmodule = self._region.getFieldmodule() self._rawDataRegion = self._region.createChild("raw_data") self._loadModel() self._loadData() self._defineDataProjectionFields() - # get centre and scale of data coordinates to manage fitting tolerances and steps + # Get centre and scale of data coordinates to manage fitting tolerances and steps. datapoints = self._fieldmodule.findNodesetByFieldDomainType(Field.DOMAIN_TYPE_DATAPOINTS) minimums, maximums = evaluate_field_nodeset_range(self._dataCoordinatesField, datapoints) self._dataCentre = mult(add(minimums, maximums), 0.5) @@ -495,11 +496,12 @@ def _loadData(self): self._discoverDataCoordinatesField() self._discoverMarkerGroup() - def run(self, endStep=None, modelFileNameStem=None, reorder = False): + def run(self, endStep=None, modelFileNameStem=None, reorder=False): """ Run either all remaining fitter steps or up to specified end step. :param endStep: Last fitter step to run, or None to run all. :param modelFileNameStem: File name stem for writing intermediate model files. + :param reorder: Reload if reordering. :return: True if reloaded (so scene changed), False if not. """ if not endStep: @@ -543,6 +545,7 @@ def _discoverDataCoordinatesField(self): """ self._dataCoordinatesField = None field = None + if self._dataCoordinatesFieldName: field = self._fieldmodule.findFieldByName(self._dataCoordinatesFieldName) if not (field and field.isValid()): diff --git a/src/scaffoldfitter/fitterstepalign.py b/src/scaffoldfitter/fitterstepalign.py index 802a314..19998a9 100644 --- a/src/scaffoldfitter/fitterstepalign.py +++ b/src/scaffoldfitter/fitterstepalign.py @@ -59,6 +59,7 @@ def __init__(self): super(FitterStepAlign, self).__init__() self._alignGroups = False self._alignMarkers = False + self._alignManually = False self._rotation = [0.0, 0.0, 0.0] self._scale = 1.0 self._scaleProportion = 1.0 @@ -78,6 +79,7 @@ def decodeSettingsJSONDict(self, dctIn: dict): dct.update(dctIn) self._alignGroups = dct["alignGroups"] self._alignMarkers = dct["alignMarkers"] + self._alignManually = dct["alignManually"] self._rotation = dct["rotation"] self._scale = dct["scale"] scaleProportion = dct.get("scaleProportion") @@ -94,6 +96,7 @@ def encodeSettingsJSONDict(self) -> dict: dct.update({ "alignGroups": self._alignGroups, "alignMarkers": self._alignMarkers, + "alignManually": self._alignManually, "rotation": self._rotation, "scale": self._scale, "scaleProportion": self._scaleProportion, @@ -132,6 +135,27 @@ def setAlignMarkers(self, alignMarkers): return True return False + def isAlignManually(self): + return self._alignManually + + def setAlignManually(self, alignManually): + if alignManually != self._alignManually: + self._alignManually = alignManually + return True + return False + + def canAlignGroups(self): + fieldmodule = self._fitter.getFieldmodule() + groups = get_group_list(fieldmodule) + return len(groups) > 0 + + def canAlignMarkers(self): + markerNodeGroup, markerLocation, markerCoordinates, markerName = self._fitter.getMarkerModelFields() + nodes_ok = markerNodeGroup is not None and markerCoordinates is not None and markerName is not None + markerDataGroup, markerDataCoordinates, markerDataName = self._fitter.getMarkerDataFields() + data_ok = markerDataGroup is not None and markerDataCoordinates is not None and markerDataName is not None + return nodes_ok and data_ok + def getRotation(self): return self._rotation @@ -197,23 +221,28 @@ def run(self, modelFileNameStem=None): """ modelCoordinates = self._fitter.getModelCoordinatesField() assert modelCoordinates, "Align: Missing model coordinates" - if self._alignGroups or self._alignMarkers: + if not self._alignManually and (self._alignGroups or self._alignMarkers): self._doAutoAlign() + + self._applyAlignment(modelCoordinates) + + self._fitter.calculateDataProjections(self) + if modelFileNameStem: + self._fitter.writeModel(modelFileNameStem + "_align.exf") + self.setHasRun(True) + + def _applyAlignment(self, model_coordinates): fieldmodule = self._fitter.getFieldmodule() with ChangeManager(fieldmodule): # rotate, scale and translate model - modelCoordinatesTransformed = createFieldsTransformations( - modelCoordinates, self._rotation, self._scale, self._translation)[0] - fieldassignment = modelCoordinates.createFieldassignment(modelCoordinatesTransformed) + model_coordinates_transformed = createFieldsTransformations( + model_coordinates, self._rotation, self._scale, self._translation)[0] + fieldassignment = model_coordinates.createFieldassignment(model_coordinates_transformed) result = fieldassignment.assign() assert result in [RESULT_OK, RESULT_WARNING_PART_DONE], "Align: Failed to transform model" self._fitter.updateModelReferenceCoordinates() del fieldassignment - del modelCoordinatesTransformed - self._fitter.calculateDataProjections(self) - if modelFileNameStem: - self._fitter.writeModel(modelFileNameStem + "_align.exf") - self.setHasRun(True) + del model_coordinates_transformed def _doAutoAlign(self): """ From 270e100cbf0187aaba3d09f1a53a79bd9a6bbf37 Mon Sep 17 00:00:00 2001 From: Hugh Sorby Date: Thu, 12 Dec 2024 14:51:37 +1300 Subject: [PATCH 15/18] Set required cmlibs.zinc to 4.0 and greater. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 00400c5..d364845 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ def readfile(filename, split=False): # minimal requirements listing "cmlibs.maths >= 0.3", "cmlibs.utils >= 0.10", - "cmlibs.zinc >= 4" + "cmlibs.zinc >= 4.0" ] readme.extend(['', 'License', '=======', '', '::', '']) source_license = readfile("LICENSE") From a25c47b7b584696859d6b4e8584ef6c25744a63f Mon Sep 17 00:00:00 2001 From: Hugh Sorby Date: Thu, 12 Dec 2024 15:54:47 +1300 Subject: [PATCH 16/18] Add canAutoAlign API. Make detection on auto-alignment better. --- src/scaffoldfitter/fitterstepalign.py | 128 +++++++++++++++++--------- tests/test_fitcube.py | 4 + 2 files changed, 86 insertions(+), 46 deletions(-) diff --git a/src/scaffoldfitter/fitterstepalign.py b/src/scaffoldfitter/fitterstepalign.py index 19998a9..5a79d49 100644 --- a/src/scaffoldfitter/fitterstepalign.py +++ b/src/scaffoldfitter/fitterstepalign.py @@ -144,17 +144,83 @@ def setAlignManually(self, alignManually): return True return False - def canAlignGroups(self): + def _alignable_group_count(self): + count = 0 fieldmodule = self._fitter.getFieldmodule() groups = get_group_list(fieldmodule) - return len(groups) > 0 + with ChangeManager(fieldmodule): + for group in groups: + dataGroup = self._fitter.getGroupDataProjectionNodesetGroup(group) + if not dataGroup: + continue + meshGroup = self._fitter.getGroupDataProjectionMeshGroup(group) + if not meshGroup: + continue + count += 1 + + return count + + def _match_markers(self): + writeDiagnostics = self.getDiagnosticLevel() > 0 + matches = {} + + markerGroup = self._fitter.getMarkerGroup() + if markerGroup is None: + if writeDiagnostics: + print("Align: No marker group to align with.") + return matches - def canAlignMarkers(self): markerNodeGroup, markerLocation, markerCoordinates, markerName = self._fitter.getMarkerModelFields() - nodes_ok = markerNodeGroup is not None and markerCoordinates is not None and markerName is not None + if markerNodeGroup is None or markerCoordinates is None or markerName is None: + if writeDiagnostics: + print("Align: No marker group, coordinates or name fields.") + + return matches + markerDataGroup, markerDataCoordinates, markerDataName = self._fitter.getMarkerDataFields() - data_ok = markerDataGroup is not None and markerDataCoordinates is not None and markerDataName is not None - return nodes_ok and data_ok + if markerDataGroup is None or markerDataCoordinates is None or markerDataName is None: + if writeDiagnostics: + print("Align: No marker data group, coordinates or name fields.") + + return matches + + modelMarkers = getNodeNameCentres(markerNodeGroup, markerCoordinates, markerName) + dataMarkers = getNodeNameCentres(markerDataGroup, markerDataCoordinates, markerDataName) + + # match model and data markers, warn of unmatched markers + for modelName in modelMarkers: + # name match allows case and whitespace differences + matchName = modelName.strip().casefold() + for dataName in dataMarkers: + if dataName.strip().casefold() == matchName: + entry_name = f"{modelName}_marker" + matches[entry_name] = (modelMarkers[modelName], dataMarkers[dataName]) + if writeDiagnostics: + print("Align: Model marker '" + modelName + "' found in data" + + (" as '" + dataName + "'" if (dataName != modelName) else "")) + dataMarkers.pop(dataName) + break + else: + if writeDiagnostics: + print("Align: Model marker '" + modelName + "' not found in data") + if writeDiagnostics: + for dataName in dataMarkers: + print("Align: Data marker '" + dataName + "' not found in model") + + return matches + + def canAutoAlign(self): + group_count = self._alignable_group_count() + matches = self._match_markers() + total = group_count + len(matches) + return total > 2 + + def canAlignGroups(self): + return self._alignable_group_count() > 2 + + def canAlignMarkers(self): + matches = self._match_markers() + return len(matches) > 2 def getRotation(self): return self._rotation @@ -264,7 +330,7 @@ def _doAutoAlign(self): meshGroup = self._fitter.getGroupDataProjectionMeshGroup(group) if not meshGroup: continue - groupName = group.getName() + groupName = f"{group.getName()}_group" # use centre of bounding box as middle of data; previous use of mean was affected by uneven density minDataCoordinates, maxDataCoordinates = evaluate_field_nodeset_range(dataCoordinates, dataGroup) middleDataCoordinates = mult(add(minDataCoordinates, maxDataCoordinates), 0.5) @@ -275,46 +341,16 @@ def _doAutoAlign(self): del one if self._alignMarkers: - markerGroup = self._fitter.getMarkerGroup() - assert markerGroup, "Align: No marker group to align with" - - markerNodeGroup, markerLocation, markerCoordinates, markerName = self._fitter.getMarkerModelFields() - assert markerNodeGroup and markerCoordinates and markerName, \ - "Align: No marker group, coordinates or name fields" - modelMarkers = getNodeNameCentres(markerNodeGroup, markerCoordinates, markerName) - - markerDataGroup, markerDataCoordinates, markerDataName = self._fitter.getMarkerDataFields() - assert markerDataGroup and markerDataCoordinates and markerDataName, \ - "Align: No marker data group, coordinates or name fields" - dataMarkers = getNodeNameCentres(markerDataGroup, markerDataCoordinates, markerDataName) - - # match model and data markers, warn of unmatched markers - writeDiagnostics = self.getDiagnosticLevel() > 0 - for modelName in modelMarkers: - # name match allows case and whitespace differences - matchName = modelName.strip().casefold() - for dataName in dataMarkers: - if dataName.strip().casefold() == matchName: - pointMap[modelName] = (modelMarkers[modelName], dataMarkers[dataName]) - if writeDiagnostics: - print("Align: Model marker '" + modelName + "' found in data" + - (" as '" + dataName + "'" if (dataName != modelName) else "")) - dataMarkers.pop(dataName) - break - else: - if writeDiagnostics: - print("Align: Model marker '" + modelName + "' not found in data") - if writeDiagnostics: - for dataName in dataMarkers: - print("Align: Data marker '" + dataName + "' not found in model") + matches = self._match_markers() + pointMap.update(matches) self._optimiseAlignment(pointMap) def getTransformationMatrix(self): - ''' + """ :return: 4x4 row-major transformation matrix with first index down rows, second across columns, suitable for multiplication p' = Mp where p = [ x, y, z, h ]. - ''' + """ # apply transformation in order: scale then rotation then translation if not all((v == 0.0) for v in self._rotation): rotationMatrix = euler_to_rotation_matrix(self._rotation) @@ -342,13 +378,13 @@ def _optimiseAlignment(self, pointMap): region = self._fitter.getContext().createRegion() fieldmodule = region.getFieldmodule() - # get centre of mass CM and span of model coordinates and data + # Get centre of mass CM and span of model coordinates and data. modelsum = [0.0, 0.0, 0.0] datasum = [0.0, 0.0, 0.0] - modelMin = copy.deepcopy(list(pointMap.values())[0][0]) - modelMax = copy.deepcopy(list(pointMap.values())[0][0]) - dataMin = copy.deepcopy(list(pointMap.values())[0][1]) - dataMax = copy.deepcopy(list(pointMap.values())[0][1]) + modelMin = [math.inf] * 3 # copy.deepcopy(list(pointMap.values())[0][0]) + modelMax = [-math.inf] * 3 # copy.deepcopy(list(pointMap.values())[0][0]) + dataMin = [math.inf] * 3 # copy.deepcopy(list(pointMap.values())[0][1]) + dataMax = [-math.inf] * 3 # copy.deepcopy(list(pointMap.values())[0][1]) for name, positions in pointMap.items(): modelx = positions[0] datax = positions[1] diff --git a/tests/test_fitcube.py b/tests/test_fitcube.py index f5593cd..ec754a3 100644 --- a/tests/test_fitcube.py +++ b/tests/test_fitcube.py @@ -180,6 +180,10 @@ def test_alignMarkersFitRegularData(self): self.assertEqual(2, len(fitter.getFitterSteps())) self.assertTrue(align.setAlignMarkers(True)) self.assertTrue(align.isAlignMarkers()) + self.assertTrue(align.canAlignMarkers()) + self.assertTrue(align.canAlignGroups()) + self.assertTrue(align.canAutoAlign()) + align.run() # fitter.getRegion().writeFile(os.path.join(here, "resources", "km_fitgeometry2.exf")) rotation = align.getRotation() From 97cea0cee26db7ffc94d652a1bd95d2ce5d51beb Mon Sep 17 00:00:00 2001 From: Hugh Sorby Date: Fri, 13 Dec 2024 10:46:43 +1300 Subject: [PATCH 17/18] When no fit parameters are set make sure old values don't get used by setting fit parameters to their identity values. --- src/scaffoldfitter/fitterstepalign.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/scaffoldfitter/fitterstepalign.py b/src/scaffoldfitter/fitterstepalign.py index 5a79d49..4afca6d 100644 --- a/src/scaffoldfitter/fitterstepalign.py +++ b/src/scaffoldfitter/fitterstepalign.py @@ -60,6 +60,13 @@ def __init__(self): self._alignGroups = False self._alignMarkers = False self._alignManually = False + self._rotation = None + self._scale = None + self._scaleProportion = None + self._translation = None + self._init_fit_parameters() + + def _init_fit_parameters(self): self._rotation = [0.0, 0.0, 0.0] self._scale = 1.0 self._scaleProportion = 1.0 @@ -289,6 +296,10 @@ def run(self, modelFileNameStem=None): assert modelCoordinates, "Align: Missing model coordinates" if not self._alignManually and (self._alignGroups or self._alignMarkers): self._doAutoAlign() + elif not self._alignManually and not (self._alignGroups or self._alignMarkers): + # Nothing is set, so make the fit do nothing by setting the fit parameters to + # their identity values. + self._init_fit_parameters() self._applyAlignment(modelCoordinates) From 0e7073b3847b3a1b4164ed693c253003402982e5 Mon Sep 17 00:00:00 2001 From: Hugh Sorby Date: Fri, 13 Dec 2024 12:20:20 +1300 Subject: [PATCH 18/18] Add matching group and marker count to align step. --- src/scaffoldfitter/fitterstepalign.py | 10 +++++++--- tests/test_fitcube.py | 2 ++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/scaffoldfitter/fitterstepalign.py b/src/scaffoldfitter/fitterstepalign.py index 4afca6d..dd0c49e 100644 --- a/src/scaffoldfitter/fitterstepalign.py +++ b/src/scaffoldfitter/fitterstepalign.py @@ -216,10 +216,14 @@ def _match_markers(self): return matches + def matchingMarkerCount(self): + return len(self._match_markers()) + + def matchingGroupCount(self): + return self._alignable_group_count() + def canAutoAlign(self): - group_count = self._alignable_group_count() - matches = self._match_markers() - total = group_count + len(matches) + total = self.matchingGroupCount() + self.matchingMarkerCount() return total > 2 def canAlignGroups(self): diff --git a/tests/test_fitcube.py b/tests/test_fitcube.py index ec754a3..23f7e48 100644 --- a/tests/test_fitcube.py +++ b/tests/test_fitcube.py @@ -183,6 +183,8 @@ def test_alignMarkersFitRegularData(self): self.assertTrue(align.canAlignMarkers()) self.assertTrue(align.canAlignGroups()) self.assertTrue(align.canAutoAlign()) + self.assertEqual(4, align.matchingMarkerCount()) + self.assertEqual(3, align.matchingGroupCount()) align.run() # fitter.getRegion().writeFile(os.path.join(here, "resources", "km_fitgeometry2.exf"))