diff --git a/.github/workflows/run_tests.yaml b/.github/workflows/run_tests.yaml new file mode 100644 index 0000000..4bec581 --- /dev/null +++ b/.github/workflows/run_tests.yaml @@ -0,0 +1,28 @@ +name: Test library + +on: + pull_request: + +jobs: + build-and-test: + runs-on: ubuntu-22.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.11 + - name: Install dependencies + run: | + sudo apt install libopengl0 libglu1-mesa -y + - 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 + run: | + python -m unittest discover -s tests/ 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..dd0c49e 100644 --- a/src/scaffoldfitter/fitterstepalign.py +++ b/src/scaffoldfitter/fitterstepalign.py @@ -59,6 +59,14 @@ def __init__(self): super(FitterStepAlign, self).__init__() 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 @@ -78,6 +86,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 +103,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 +142,97 @@ 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 _alignable_group_count(self): + count = 0 + fieldmodule = self._fitter.getFieldmodule() + groups = get_group_list(fieldmodule) + 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 + + markerNodeGroup, markerLocation, markerCoordinates, markerName = self._fitter.getMarkerModelFields() + 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() + 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 matchingMarkerCount(self): + return len(self._match_markers()) + + def matchingGroupCount(self): + return self._alignable_group_count() + + def canAutoAlign(self): + total = self.matchingGroupCount() + self.matchingMarkerCount() + 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 @@ -197,23 +298,32 @@ 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() + 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) + + 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): """ @@ -235,7 +345,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) @@ -246,46 +356,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) @@ -313,13 +393,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/resources/two_element_cube.exf b/tests/resources/two_element_cube.exf new file mode 100644 index 0000000..f5b01f6 --- /dev/null +++ b/tests/resources/two_element_cube.exf @@ -0,0 +1,373 @@ +EX Version: 3 +Region: / +!#nodeset nodes +Define node template: node1 +Shape. Dimension=0 +#Fields=1 +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) +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 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 1.000000000000000e+00 0.000000000000000e+00 + 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 5.000000000000000e-01 +Node: 4 + 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.000000000000000e-01 +Node: 6 + 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.000000000000000e-01 +Node: 7 + 0.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: 8 + 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 5.000000000000000e-01 +Node: 10 + 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 5.000000000000000e-01 +Node: 11 + 0.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 +Node: 12 + 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 +#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) 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 +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 23b9221..23f7e48 100644 --- a/tests/test_fitcube.py +++ b/tests/test_fitcube.py @@ -2,7 +2,9 @@ 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.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 from cmlibs.zinc.result import RESULT_OK @@ -178,6 +180,12 @@ 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()) + self.assertEqual(4, align.matchingMarkerCount()) + self.assertEqual(3, align.matchingGroupCount()) + align.run() # fitter.getRegion().writeFile(os.path.join(here, "resources", "km_fitgeometry2.exf")) rotation = align.getRotation() @@ -765,6 +773,40 @@ 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) + data_region = context.createRegion() + data_region.readFile(os.path.join(here, "resources", "cube_to_sphere_data_random.exf")) + + fm = region.getFieldmodule() + + 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) + + # 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(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()