Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ docs/build/*
docs/source/_build/*
src/idvc/version.py
build/*
*.profraw
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,21 @@ iDVC depends on code initially developed by Prof. Brian K. Bay and collaborators
[1] B. K. Bay, T. S. Smith, D. P. Fyhrie, M. Saad "Digital volume correlation: Three-dimensional strain mapping using x-ray tomography", *Experimental Mechanics* **39** 217–226, 1999. [DOI: 10.1007/BF02323555](https://doi.org/10.1007/BF02323555)

[2] B. K. Bay "Methods and applications of digital volume correlation", *J. Strain Analysis* **43** 745-760, 2008. [DOI: 10.1243/03093247JSA436](https://doi.org/10.1243/03093247JSA436)


# MacOS + Qt + VTK

`conda-forge`'s VTK package depends on Qt6 and we only tested this app with Qt5. On MacOS this seems to be a problem as the OS finds 2 files which provide the same functionality so it raises an error and nothing works. The suggested workaround is to install the CILViewer (from the repo), `PySide2` (`conda-forge`) and [VTK](https://docs.vtk.org/en/latest/advanced/available_python_wheels.html) (from Kitware's wheels) manually.


```bash
pip install --extra-index-url https://wheels.vtk.org vtk
```

```yaml
dependencies:
- pip
- pip:
- --extra-index-url https://wheels.vtk.org
- vtk
```
3 changes: 2 additions & 1 deletion recipe/dev_environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ dependencies:
- pyside2
- scipy
- ccpi::ccpi-viewer >=25.0.0
- ccpi::ccpi-dvc >=22.0.0
- ccpi/label/dev::ccpi-dvc >=25.0.0
- natsort
- docopt
- matplotlib
- openmp # [osx]
- qdarkstyle
- vtk
- filetype
29 changes: 29 additions & 0 deletions recipe/dev_environment_osx.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: idvc_dev
channels:
- conda-forge
- ccpi
dependencies:
# - eqt >=1.0.1
- openpyxl
- python
- numpy
- pandas
- pyside2
- scipy
# - ccpi::ccpi-viewer >=25.0.0
- ccpi/label/dev::ccpi-dvc >=25.0.0
- natsort
- docopt
- matplotlib
- llvm-openmp
- qdarkstyle
- h5py
- pyyaml
- schema
- qtpy
- filetype
- pip:
- --extra-index-url https://wheels.vtk.org
- vtk
- git+https://github.com/TomographicImaging/CILViewer.git#subdirectory=Wrappers/Python
- eqt >=1.0.1
4 changes: 2 additions & 2 deletions recipe/meta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ requirements:
- pandas
- pyside2
- scipy
- ccpi-viewer >=25.0.0
- ccpi-dvc >=22.0.0
- ccpi::ccpi-viewer >=25.0.0
- ccpi/label/dev::ccpi-dvc >=25.0.0
- natsort
- docopt
- matplotlib
Expand Down
4 changes: 2 additions & 2 deletions src/idvc/dvc_config_template.txt
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ min_vol_fract {min_vol_fract} ### only search if subvol fraction is greater th
disp_max {disp_max} ### in voxels, used for range checking and global search limits
num_srch_dof {num_srch_dof} ### 3, 6, or 12
obj_function {obj_function} ### sad, ssd, zssd, nssd, znssd
interp_type {interp_type} ### trilinear, tricubic

interp_type {interp_type} ### trilinear, tricubic, tri_bspline_3, tri_bspline_5, tri_bspline_7
{bspline_order}
### optional parameters tuning and refining the search process

rigid_trans {rigid_trans} ### rigid body offset of target volume, in voxels
Expand Down
24 changes: 14 additions & 10 deletions src/idvc/dvc_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
from ccpi.viewer.utils.colormaps import CILColorMaps

from natsort import natsorted
import imghdr
import filetype
import glob

# from vtk.numpy_interface import algorithms as algs
Expand Down Expand Up @@ -762,8 +762,9 @@ def copy_file(self, start_location, end_location, image_var, **kwargs):
N = len(start_location)
for i,fs in enumerate(zip(start_location, end_location)):
if N > 1:
ftype = imghdr.what(fs[0])
if ftype != 'tiff':
kind = filetype.guess(fs[0])
ftype = kind.extension if kind is not None else ""
if ftype not in ['tiff', 'tif']:
raise ValueError('File type not supported: {}'.format(fs[0]))
progress_callback.emit(i/N*100)
shutil.copyfile(fs[0], fs[1])
Expand Down Expand Up @@ -4384,6 +4385,9 @@ def CreateRunDVCPanel(self):
rdvc_widgets['run_iterp_type_entry'].addItem('Nearest')
rdvc_widgets['run_iterp_type_entry'].addItem('Trilinear')
rdvc_widgets['run_iterp_type_entry'].addItem('Tricubic')
rdvc_widgets['run_iterp_type_entry'].addItem('tri_bspline_3')
rdvc_widgets['run_iterp_type_entry'].addItem('tri_bspline_5')
rdvc_widgets['run_iterp_type_entry'].addItem('tri_bspline_7')
rdvc_widgets['run_iterp_type_entry'].setCurrentIndex(2)
rdvc_widgets['run_iterp_type_entry'].setToolTip(interp_text)
formLayout.setWidget(widgetno, QFormLayout.FieldRole, rdvc_widgets['run_iterp_type_entry'])
Expand Down Expand Up @@ -4753,15 +4757,15 @@ def create_run_config(self, **kwargs):
run_config['subvol_geom'] = self.pointcloud_parameters['pointcloud_volume_shape_entry'].currentText().lower()
run_config['subvol_npts'] = self.subvolume_points

run_config['disp_max'] = self.rdvc_widgets['run_max_displacement_entry'].value(), #38 for test image
run_config['step_max'] = self.rdvc_widgets['run_max_displacement_entry'].value(), #38 for test image
run_config['dof'] = self.rdvc_widgets['run_ndof_entry'].currentText()
run_config['obj'] = self.rdvc_widgets['run_objf_entry'].currentText()
run_config['interp_type'] = self.rdvc_widgets['run_iterp_type_entry'].currentText().lower()

if (hasattr(self, 'translate')):
run_config['rigid_trans'] = str(self.translate.GetTranslation()[0]*-1) + " " + str(self.translate.GetTranslation()[1]*-1) + " " + str(self.translate.GetTranslation()[2]*-1)
run_config['start_estimate'] = str(self.translate.GetTranslation()[0]*-1) + " " + str(self.translate.GetTranslation()[1]*-1) + " " + str(self.translate.GetTranslation()[2]*-1)
else:
run_config['rigid_trans']= "0.0 0.0 0.0"
run_config['start_estimate']= "0.0 0.0 0.0"

self.run_folder = "Results/" + folder_name
run_config['run_folder'] = self.run_folder
Expand Down Expand Up @@ -5006,7 +5010,7 @@ def CreateViewDVCResultsPanel(self):

scroll_area = dockWidget.widget()
scroll_area.apply_qdarkstyle_to_buttons(self.dvc_results_panel[1])

def show_run_pcs(self):
#show pointcloud files in list
self.result_widgets['pc_entry'].clear()
Expand Down Expand Up @@ -5040,16 +5044,16 @@ def LoadResultsOnViewer(self):
try:
subvol_size = int(self.result_widgets['pc_entry'].currentText())
except ValueError as ve:
self.warningDialog("Invalid input at Subvolume Size", "Error")
self.warningDialog(message="Invalid input at Subvolume Size", window_title="Parse Error", detailed_text=f"{ve}")
return
try:
subvol_points = int(self.result_widgets['subvol_entry'].currentText())
except ValueError as ve:
self.warningDialog("Invalid input at Subvolume Size", "Error")
self.warningDialog(message="Invalid input at Subvolume Entry", window_title="Parse Error", detailed_text=f"{ve}")
return

if(subvol_points == ""):
self.warningDialog("An error occurred with this run so the results could not be displayed.", "Error")
self.warningDialog(message="An error occurred with this run so the results could not be displayed.", window_title="Error")
return


Expand Down
108 changes: 49 additions & 59 deletions src/idvc/dvc_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,25 +32,16 @@ def emit(self, *args, **kwargs):
count = 0
runs_completed = 0

blank_config = '''###############################################################################
#
#
# example dvc process control file
#
#
###############################################################################

# all lines beginning with a # character are ignored
# some parameters are conditionally required, depending on the setting of other parameters
# for example, if subvol_thresh is off, the threshold description parameters are not required

blank_config = '''
### file names

reference_filename {reference_filename} ### reference tomography image volume
correlate_filename {correlate_filename} ### correlation tomography image volume
reference_filename {reference_filename} ### single raster file (raw) of same dimensions as the correlate_volume
correlate_filename {correlate_filename} ### single raster file (raw) of same dimensions as the reference_volume

point_cloud_filename {point_cloud_filename} ### tab/space/comma delimited text file with point cloud labels (n) and (xyz) locations
output_filename {output_filename} ### base name for run status (.stat) and displacement data (.disp) output files

point_cloud_filename {point_cloud_filename} ### file of search point locations
output_filename {output_filename} ### base name for output files
num_points_to_process {num_points_to_process} ### Number of points in the point cloud to process

### description of the image data files, all must be the same size and structure

Expand All @@ -59,33 +50,35 @@ def emit(self, *args, **kwargs):
vol_wide {vol_wide} ### width in pixels of each slice
vol_high {vol_high} ### height in pixels of each slice
vol_tall {vol_tall} ### number of slices in the stack
vol_endian {vol_endian} ### big or little endian byte ordering
vol_endian {vol_endian} ### big or little endian byte ordering, ignored if vol_bit_depth is 8

### parameters defining the subvolumes that will be created at each search point

subvol_geom {subvol_geom} ### cube, sphere
subvol_geom {subvol_geom} ### cube, sphere: geometry of the subvolumes
subvol_size {subvol_size} ### side length or diameter, in voxels
subvol_npts {subvol_npts} ### number of points to distribute within the subvol
subvol_aspect {subvol_aspect} ### stretch or contract the subvolume in any coordinate direction between 0.5 and 2.0

subvol_thresh {subvol_thresh} ### on or off, evaluate subvolumes based on threshold
gray_thresh_min {gray_thresh_min} ### lower limit of a gray threshold range if subvol_thresh is on
gray_thresh_max {gray_thresh_max} ### upper limit of a gray threshold range if subvol_thresh is on
min_vol_fract {min_vol_fract} ### only search if subvol fraction is greater than

### required parameters defining the basic the search process
###
### Keywords in group opt_mthd
###

disp_max {disp_max} ### in voxels, used for range checking and global search limits
num_srch_dof {num_srch_dof} ### 3, 6, or 12
obj_function {obj_function} ### sad, ssd, zssd, nssd, znssd
interp_type {interp_type} ### trilinear, tricubic
num_srch_dof {num_srch_dof} ### 3 (translation), 6 (+rotation), or 12 (+strain)
obj_function {obj_function} ### ssd, zssd, nssd, znssd
interp_type {interp_type} ### trilinear tricubic tri_bspline_3 tri_bspline_5 tri_bspline_7
start_position {starting_point} ### x,y,z location of starting point for DVC analysisstart_estimate
start_estimate {start_estimate} ### x,y,z displacement (u,v,w) in voxels at the start_position
step_max {step_max} ### maximum parameter step allowed during optimization, see also step_tol

### optional parameters tuning and refining the search process

rigid_trans {rigid_trans} ### rigid body offset of target volume, in voxels
basin_radius {basin_radius} ### coarse-search resolution, in voxels, 0.0 = none
subvol_aspect {subvol_aspect} ### subvolume aspect ratio
num_points_to_process {num_points_to_process} ### Number of points in the point cloud to process
starting_point {starting_point} ### x,y,z location of starting point for DVC analysis
###
### Keywords in group opt_tune
###

# cost_tol 1.0e-06 ### optional tuning of objective function convergence tolerance
# step_tol 1.0e-04 ### optional tuning of parameter vector convergence tolerance
# max_iter 20 ### optional change in the maximum number of optimization iterations
'''

def update_progress(main_window, process, total_points, required_runs, run_succeeded, start_time, num_points_to_process):
Expand Down Expand Up @@ -232,7 +225,8 @@ def set_up(self, *args, **kwargs):
- disp_max: maximum displacement in voxels
- dof: number of parameters in the optimisation, 3,6, or 12
- obj: objective function, either sad, ssd, zssd, nssd, or znssd
- interp_type: interpolation type, either trilinear or tricubic
- interp_type: interpolation type, either trilinear, tricubic, or bspline
- bspline_order: order of the B-spline interpolation, only used if interp_type is bspline
- run_folder: folder to save the run results and configurations
- rigid_trans: rigid translation between the reference and correlation volumes
- point0_world_coordinate: world coordinates of the starting point for the DVC analysis
Expand Down Expand Up @@ -314,13 +308,13 @@ def set_up(self, *args, **kwargs):

subvol_npts = config['subvol_npts']

disp_max = int(config['disp_max'][0]) #TODO: need to change
step_max = int(config['step_max'][0]) #TODO: need to change
dof = int(config['dof'])
obj = config['obj']
interp_type = config['interp_type']

rigid_trans = config['rigid_trans']
starting_point = config['point0_world_coordinate']
start_estimate = config['start_estimate']
start_position = config['point0_world_coordinate']

# Change directory into the folder where the run will be saved:
os.chdir(self.session_folder)
Expand Down Expand Up @@ -394,33 +388,29 @@ def set_up(self, *args, **kwargs):
return



config = blank_config.format(
reference_filename= reference_file, # reference tomography image volume
correlate_filename= correlate_file, # correlation tomography image volume
reference_filename= reference_file, # reference tomography image volume
correlate_filename= correlate_file, # correlation tomography image volume
point_cloud_filename = grid_roi_fname,
output_filename= output_filename,
output_filename= output_filename,
vol_bit_depth= vol_bit_depth, # get from ref, 8 or 16
vol_hdr_lngth=vol_hdr_lngth,# get from ref, fixed-length header size, may be zero
vol_wide= dims[0], # number of x slices
vol_high= dims[1], # number of y slices
vol_tall= dims[2], #number of z slices
vol_endian = endian,
subvol_geom= subvol_geom,
subvol_size= subvolume_size,
subvol_npts= subvolume_point,
subvol_thresh='off',
gray_thresh_min='27',
gray_thresh_max='127',
min_vol_fract='0.2',
disp_max= disp_max, #38 for test image
num_srch_dof= dof, #6 for test image
obj_function= obj,
interp_type= interp_type, #tricubic for test image
rigid_trans= rigid_trans, #translation between ref and cor - determined from image registration
basin_radius='0.0',
vol_hdr_lngth= vol_hdr_lngth,# get from ref, fixed-length header size, may be zero
vol_wide= dims[0], # number of x slices
vol_high= dims[1], # number of y slices
vol_tall= dims[2], #number of z slices
vol_endian = endian,
subvol_geom= subvol_geom,
subvol_size= subvolume_size,
subvol_npts= subvolume_point,
step_max= step_max, #38 for test image
num_srch_dof= dof, #6 for test image
obj_function= obj,
interp_type= interp_type, #tricubic for test image
start_estimate= start_estimate, #translation between ref and cor - determined from image registration
subvol_aspect='1.0 1.0 1.0',# image spacing
num_points_to_process=num_points_to_process,
starting_point='{} {} {}'.format(*starting_point))
starting_point='{} {} {}'.format(*start_position))
time.sleep(1)
with open(config_filename,"w") as config_file:
config_file.write(config)
Expand Down
5 changes: 3 additions & 2 deletions src/idvc/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
# Author: Laura Murgatroyd (UKRI-STFC)
# Author: Edoardo Pasca (UKRI-STFC)

import imghdr
import filetype
import os
import shutil
import sys
Expand Down Expand Up @@ -116,7 +116,8 @@ def getFileExtension(self):
file_extension = os.path.splitext(self.image)[1]
else:
for image in self.image_files:
file_extension = imghdr.what(image)
kind = filetype.guess(image)
file_extension = kind.extension if kind is not None else ""
if file_extension.lower() not in ['tiff', 'tif']:
error_title = "Read Error"
error_text = "Error reading file: ({filename})".format(
Expand Down
Loading
Loading