Mercurial > repos > rv43 > tomo
comparison tomo.py @ 7:845270a96464 draft
"planemo upload for repository https://github.com/rolfverberg/galaxytools commit a08ed1264ea7b7c878ba96801e4b27ef621fb6be"
| author | rv43 |
|---|---|
| date | Thu, 07 Apr 2022 21:09:58 +0000 |
| parents | f9c52762c32c |
| children | ec7c3e84d611 |
comparison
equal
deleted
inserted
replaced
| 6:baf9f5eef128 | 7:845270a96464 |
|---|---|
| 1164 save_fig=self.save_plots, save_only=self.save_plots_only, cmap='gray', | 1164 save_fig=self.save_plots, save_only=self.save_plots_only, cmap='gray', |
| 1165 vmin=vmin, vmax=vmax) | 1165 vmin=vmin, vmax=vmax) |
| 1166 del edges | 1166 del edges |
| 1167 | 1167 |
| 1168 def _findCenterOnePlane(self, sinogram, row, thetas_deg, eff_pixel_size, cross_sectional_dim, | 1168 def _findCenterOnePlane(self, sinogram, row, thetas_deg, eff_pixel_size, cross_sectional_dim, |
| 1169 tol=0.1, num_core=1, recon_pngname=None): | 1169 tol=0.1, num_core=1, recon_pngname=None, galaxy_param=None): |
| 1170 """Find center for a single tomography plane. | 1170 """Find center for a single tomography plane. |
| 1171 """ | 1171 """ |
| 1172 # sinogram index order: theta,column | 1172 # sinogram index order: theta,column |
| 1173 # need index order column,theta for iradon, so take transpose | 1173 # need index order column,theta for iradon, so take transpose |
| 1174 sinogram_T = sinogram.T | 1174 sinogram_T = sinogram.T |
| 1175 center = sinogram.shape[1]/2 | 1175 center = sinogram.shape[1]/2 |
| 1176 | 1176 |
| 1177 # try automatic center finding routines for initial value | 1177 # try automatic center finding routines for initial value |
| 1178 tomo_center = tomopy.find_center_vo(sinogram, ncore=num_core) | 1178 tomo_center = tomopy.find_center_vo(sinogram, ncore=num_core) |
| 1179 center_offset_vo = tomo_center-center | 1179 center_offset_vo = tomo_center-center |
| 1180 if self.test_mode or self.galaxy_flag: | 1180 if self.test_mode: |
| 1181 logging.info(f'Center at row {row} using Nghia Vo’s method = {center_offset_vo:.2f}') | 1181 logging.info(f'Center at row {row} using Nghia Vo’s method = {center_offset_vo:.2f}') |
| 1182 if self.test_mode: | 1182 del sinogram_T |
| 1183 return float(center_offset_vo) | |
| 1184 elif self.galaxy_flag: | |
| 1185 logging.info(f'Center at row {row} using Nghia Vo’s method = {center_offset_vo:.2f}') | |
| 1186 if recon_pngname: | |
| 1187 assert(isinstance(recon_pngname, str)) | |
| 1188 recon_plane = self._reconstructOnePlane(sinogram_T, tomo_center, thetas_deg, | |
| 1189 eff_pixel_size, cross_sectional_dim, False, num_core) | |
| 1190 title = os.path.basename(recon_pngname) | |
| 1191 self._plotEdgesOnePlane(recon_plane, title, name=recon_pngname) | |
| 1192 del recon_plane | |
| 1193 if not galaxy_param or not galaxy_param['center_type_selector']: | |
| 1183 del sinogram_T | 1194 del sinogram_T |
| 1184 return float(center_offset_vo) | 1195 return float(center_offset_vo) |
| 1185 else: | 1196 else: |
| 1186 print(f'Center at row {row} using Nghia Vo’s method = {center_offset_vo:.2f}') | 1197 print(f'Center at row {row} using Nghia Vo’s method = {center_offset_vo:.2f}') |
| 1187 if recon_pngname: | 1198 if recon_pngname: |
| 1188 logging.warning('Ignoring recon_pngname in _findCenterOnePlane (only for Galaxy)') | 1199 logging.warning('Ignoring recon_pngname in _findCenterOnePlane (only for Galaxy)') |
| 1189 recon_plane = self._reconstructOnePlane(sinogram_T, tomo_center, thetas_deg, | 1200 recon_plane = self._reconstructOnePlane(sinogram_T, tomo_center, thetas_deg, |
| 1190 eff_pixel_size, cross_sectional_dim, False, num_core) | 1201 eff_pixel_size, cross_sectional_dim, False, num_core) |
| 1191 if self.galaxy_flag: | |
| 1192 assert(isinstance(recon_pngname, str)) | |
| 1193 title = os.path.basename(recon_pngname) | |
| 1194 self._plotEdgesOnePlane(recon_plane, title, name=recon_pngname) | |
| 1195 del sinogram_T | |
| 1196 del recon_plane | |
| 1197 return float(center_offset_vo) | |
| 1198 else: | |
| 1199 title = f'edges row{row} center_offset_vo{center_offset_vo:.2f}' | 1202 title = f'edges row{row} center_offset_vo{center_offset_vo:.2f}' |
| 1200 self._plotEdgesOnePlane(recon_plane, title) | 1203 self._plotEdgesOnePlane(recon_plane, title) |
| 1201 if (pyip.inputYesNo('Try finding center using phase correlation '+ | 1204 if not self.galaxy_flag: |
| 1202 '(y/[n])? ', blank=True) == 'yes'): | 1205 if (pyip.inputYesNo('Try finding center using phase correlation '+ |
| 1203 tomo_center = tomopy.find_center_pc(sinogram, sinogram, tol=0.1, | 1206 '(y/[n])? ', blank=True) == 'yes'): |
| 1204 rotc_guess=tomo_center) | 1207 tomo_center = tomopy.find_center_pc(sinogram, sinogram, tol=0.1, |
| 1205 error = 1. | |
| 1206 while error > tol: | |
| 1207 prev = tomo_center | |
| 1208 tomo_center = tomopy.find_center_pc(sinogram, sinogram, tol=tol, | |
| 1209 rotc_guess=tomo_center) | 1208 rotc_guess=tomo_center) |
| 1210 error = np.abs(tomo_center-prev) | 1209 error = 1. |
| 1211 center_offset = tomo_center-center | 1210 while error > tol: |
| 1212 print(f'Center at row {row} using phase correlation = {center_offset:.2f}') | 1211 prev = tomo_center |
| 1213 recon_plane = self._reconstructOnePlane(sinogram_T, tomo_center, thetas_deg, | 1212 tomo_center = tomopy.find_center_pc(sinogram, sinogram, tol=tol, |
| 1214 eff_pixel_size, cross_sectional_dim, False, num_core) | 1213 rotc_guess=tomo_center) |
| 1215 title = f'edges row{row} center_offset{center_offset:.2f}' | 1214 error = np.abs(tomo_center-prev) |
| 1216 self._plotEdgesOnePlane(recon_plane, title) | 1215 center_offset = tomo_center-center |
| 1217 if (pyip.inputYesNo('Accept a center location ([y]) or continue '+ | 1216 print(f'Center at row {row} using phase correlation = {center_offset:.2f}') |
| 1218 'search (n)? ', blank=True) != 'no'): | 1217 recon_plane = self._reconstructOnePlane(sinogram_T, tomo_center, thetas_deg, |
| 1219 del sinogram_T | 1218 eff_pixel_size, cross_sectional_dim, False, num_core) |
| 1220 del recon_plane | 1219 title = f'edges row{row} center_offset{center_offset:.2f}' |
| 1221 center_offset = pyip.inputNum( | 1220 self._plotEdgesOnePlane(recon_plane, title) |
| 1222 f' Enter chosen center offset [{-int(center)}, {int(center)}] '+ | 1221 if (pyip.inputYesNo('Accept a center location ([y]) or continue '+ |
| 1223 f'([{center_offset_vo}])): ', blank=True) | 1222 'search (n)? ', blank=True) != 'no'): |
| 1224 if center_offset == '': | 1223 center_offset = pyip.inputNum( |
| 1225 center_offset = center_offset_vo | 1224 f' Enter chosen center offset [{-int(center)}, {int(center)}] '+ |
| 1226 return float(center_offset) | 1225 f'([{center_offset_vo:.2f}])): ', blank=True) |
| 1227 | 1226 if center_offset == '': |
| 1227 center_offset = center_offset_vo | |
| 1228 del sinogram_T | |
| 1229 del recon_plane | |
| 1230 return float(center_offset) | |
| 1231 | |
| 1232 # perform center finding search | |
| 1228 while True: | 1233 while True: |
| 1229 center_offset_low = pyip.inputInt('\nEnter lower bound for center offset '+ | 1234 if self.galaxy_flag and galaxy_param and galaxy_param['center_type_selector']: |
| 1230 f'[{-int(center)}, {int(center)}]: ', min=-int(center), max=int(center)) | 1235 set_center = center_offset_vo |
| 1231 center_offset_upp = pyip.inputInt('Enter upper bound for center offset '+ | 1236 if galaxy_param['center_type_selector'] == 'user': |
| 1232 f'[{center_offset_low}, {int(center)}]: ', | 1237 set_center = galaxy_param['set_center'] |
| 1233 min=center_offset_low, max=int(center)) | 1238 set_range = galaxy_param['set_range'] |
| 1234 if center_offset_upp == center_offset_low: | 1239 center_offset_step = galaxy_param['set_step'] |
| 1235 center_offset_step = 1 | 1240 if (not msnc.is_num(set_range, 0) or not msnc.is_num(center_offset_step) or |
| 1236 else: | 1241 center_offset_step <= 0): |
| 1237 center_offset_step = pyip.inputInt('Enter step size for center offset search '+ | 1242 logging.warning('Illegal center finding search parameter, skip search') |
| 1238 f'[1, {center_offset_upp-center_offset_low}]: ', | 1243 del sinogram_T |
| 1239 min=1, max=center_offset_upp-center_offset_low) | 1244 return float(center_offset_vo) |
| 1240 for center_offset in range(center_offset_low, center_offset_upp+center_offset_step, | 1245 set_range = center_offset_step*max(1, int(set_range/center_offset_step)) |
| 1241 center_offset_step): | 1246 center_offset_low = set_center-set_range |
| 1242 logging.info(f'center_offset = {center_offset}') | 1247 center_offset_upp = set_center+set_range |
| 1248 else: | |
| 1249 center_offset_low = pyip.inputInt('\nEnter lower bound for center offset '+ | |
| 1250 f'[{-int(center)}, {int(center)}]: ', min=-int(center), max=int(center)) | |
| 1251 center_offset_upp = pyip.inputInt('Enter upper bound for center offset '+ | |
| 1252 f'[{center_offset_low}, {int(center)}]: ', | |
| 1253 min=center_offset_low, max=int(center)) | |
| 1254 if center_offset_upp == center_offset_low: | |
| 1255 center_offset_step = 1 | |
| 1256 else: | |
| 1257 center_offset_step = pyip.inputInt('Enter step size for center offset search '+ | |
| 1258 f'[1, {center_offset_upp-center_offset_low}]: ', | |
| 1259 min=1, max=center_offset_upp-center_offset_low) | |
| 1260 num_center_offset = 1+int((center_offset_upp-center_offset_low)/center_offset_step) | |
| 1261 center_offsets = np.linspace(center_offset_low, center_offset_upp, num_center_offset) | |
| 1262 for center_offset in center_offsets: | |
| 1263 if center_offset == center_offset_vo: | |
| 1264 continue | |
| 1265 logging.info(f'center_offset = {center_offset:.2f}') | |
| 1243 recon_plane = self._reconstructOnePlane(sinogram_T, center_offset+center, | 1266 recon_plane = self._reconstructOnePlane(sinogram_T, center_offset+center, |
| 1244 thetas_deg, eff_pixel_size, cross_sectional_dim, False, num_core) | 1267 thetas_deg, eff_pixel_size, cross_sectional_dim, False, num_core) |
| 1245 title = f'edges row{row} center_offset{center_offset}' | 1268 title = f'edges row{row} center_offset{center_offset:.2f}' |
| 1246 self._plotEdgesOnePlane(recon_plane, title) | 1269 self._plotEdgesOnePlane(recon_plane, title) |
| 1247 if pyip.inputInt('\nContinue (0) or end the search (1): ', min=0, max=1): | 1270 if self.galaxy_flag or pyip.inputInt('\nContinue (0) or end the search (1): ', |
| 1271 min=0, max=1): | |
| 1248 break | 1272 break |
| 1249 | 1273 |
| 1250 del sinogram_T | 1274 del sinogram_T |
| 1251 del recon_plane | 1275 del recon_plane |
| 1252 center_offset = pyip.inputNum(f' Enter chosen center offset '+ | 1276 if self.galaxy_flag: |
| 1277 center_offset = center_offset_vo | |
| 1278 else: | |
| 1279 center_offset = pyip.inputNum(f' Enter chosen center offset '+ | |
| 1253 f'[{-int(center)}, {int(center)}]: ', min=-int(center), max=int(center)) | 1280 f'[{-int(center)}, {int(center)}]: ', min=-int(center), max=int(center)) |
| 1254 return float(center_offset) | 1281 return float(center_offset) |
| 1255 | 1282 |
| 1256 def _reconstructOneTomoStack(self, tomo_stack, thetas, row_bounds=None, | 1283 def _reconstructOneTomoStack(self, tomo_stack, thetas, row_bounds=None, |
| 1257 center_offsets=[], sigma=0.1, rwidth=30, num_core=1, algorithm='gridrec', | 1284 center_offsets=[], sigma=0.1, rwidth=30, num_core=1, algorithm='gridrec', |
| 1511 pixel_size *= img_x_bounds[0] | 1538 pixel_size *= img_x_bounds[0] |
| 1512 for stack in stacks: | 1539 for stack in stacks: |
| 1513 stack['ref_height'] = stack['ref_height']+pixel_size | 1540 stack['ref_height'] = stack['ref_height']+pixel_size |
| 1514 self.cf.saveFile(self.config_out) | 1541 self.cf.saveFile(self.config_out) |
| 1515 | 1542 |
| 1516 def findCenters(self, row_bounds=None, center_rows=None, recon_low_pngname=None, | 1543 def findCenters(self, galaxy_param=None, num_core=None): |
| 1517 recon_upp_pngname=None, num_core=None): | |
| 1518 """Find rotation axis centers for the tomography stacks. | 1544 """Find rotation axis centers for the tomography stacks. |
| 1519 """ | 1545 """ |
| 1520 if num_core is None: | 1546 if num_core is None: |
| 1521 num_core = self.num_core | 1547 num_core = self.num_core |
| 1522 logging.debug('Find centers for tomography stacks') | 1548 logging.debug('Find centers for tomography stacks') |
| 1523 stacks = self.config['stack_info']['stacks'] | 1549 stacks = self.config['stack_info']['stacks'] |
| 1524 available_stacks = [stack['index'] for stack in stacks if stack.get('preprocessed', False)] | 1550 available_stacks = [stack['index'] for stack in stacks if stack.get('preprocessed', False)] |
| 1525 logging.debug('Available stacks: {available_stacks}') | 1551 logging.debug('Available stacks: {available_stacks}') |
| 1526 if self.galaxy_flag: | 1552 if self.galaxy_flag: |
| 1527 assert(isinstance(row_bounds, list) and len(row_bounds) == 2) | 1553 assert(isinstance(galaxy_param, dict)) |
| 1528 assert(isinstance(center_rows, list) and len(center_rows) == 2) | 1554 row_bounds = galaxy_param['row_bounds'] |
| 1529 assert(isinstance(recon_low_pngname, str)) | 1555 center_rows = galaxy_param['center_rows'] |
| 1530 assert(isinstance(recon_upp_pngname, str)) | 1556 if center_rows is None: |
| 1531 else: | 1557 logging.error('Missing center_rows entry in galaxy_param') |
| 1532 if row_bounds: | 1558 return |
| 1533 logging.warning('Ignoring row_bounds in findCenters (only for Galaxy)') | 1559 center_type_selector = galaxy_param['center_type_selector'] |
| 1534 row_bounds = None | 1560 if center_type_selector: |
| 1535 if center_rows: | 1561 if center_type_selector == 'vo': |
| 1536 logging.warning('Ignoring center_rows in findCenters (only for Galaxy)') | 1562 set_center = None |
| 1537 center_rows = None | 1563 elif center_type_selector == 'user': |
| 1538 if recon_low_pngname: | 1564 set_center = galaxy_param['set_center'] |
| 1539 logging.warning('Ignoring recon_low_pngname in findCenters (only for Galaxy)') | 1565 else: |
| 1540 recon_low_pngname = None | 1566 logging.error('Illegal center_type_selector entry in galaxy_param '+ |
| 1541 if recon_upp_pngname: | 1567 f'({center_type_selector})') |
| 1542 logging.warning('Ignoring recon_upp_pngname in findCenters (only for Galaxy)') | 1568 galaxy_param['center_type_selector'] = None |
| 1543 recon_upp_pngname = None | 1569 else: |
| 1570 if galaxy_param: | |
| 1571 logging.warning('Ignoring galaxy_param in findCenters (only for Galaxy)') | |
| 1572 galaxy_param = None | |
| 1544 | 1573 |
| 1545 # Check for valid available center stack index | 1574 # Check for valid available center stack index |
| 1546 find_center = self.config.get('find_center') | 1575 find_center = self.config.get('find_center') |
| 1547 center_stack_index = None | 1576 center_stack_index = None |
| 1548 if find_center and 'center_stack_index' in find_center: | 1577 if find_center and 'center_stack_index' in find_center: |
| 1608 find_center['center_stack_index'] = center_stack_index | 1637 find_center['center_stack_index'] = center_stack_index |
| 1609 if not self.galaxy_flag: | 1638 if not self.galaxy_flag: |
| 1610 row_bounds = find_center.get('row_bounds', None) | 1639 row_bounds = find_center.get('row_bounds', None) |
| 1611 center_rows = [find_center.get('lower_row', None), | 1640 center_rows = [find_center.get('lower_row', None), |
| 1612 find_center.get('upper_row', None)] | 1641 find_center.get('upper_row', None)] |
| 1642 if row_bounds is None: | |
| 1643 row_bounds = [0, center_stack.shape[0]] | |
| 1644 if row_bounds[0] == -1: | |
| 1645 row_bounds[0] = 0 | |
| 1646 if row_bounds[1] == -1: | |
| 1647 row_bounds[1] = center_stack.shape[0] | |
| 1648 if not msnc.is_index_range(row_bounds, 0, center_stack.shape[0]): | |
| 1649 msnc.illegal_value('row_bounds', row_bounds) | |
| 1650 return | |
| 1613 | 1651 |
| 1614 # Set thetas (in degrees) | 1652 # Set thetas (in degrees) |
| 1615 theta_range = self.config['theta_range'] | 1653 theta_range = self.config['theta_range'] |
| 1616 theta_start = theta_range['start'] | 1654 theta_start = theta_range['start'] |
| 1617 theta_end = theta_range['end'] | 1655 theta_end = theta_range['end'] |
| 1685 | 1723 |
| 1686 # Lower row center | 1724 # Lower row center |
| 1687 use_row = 'no' | 1725 use_row = 'no' |
| 1688 use_center = 'no' | 1726 use_center = 'no' |
| 1689 row = center_rows[0] | 1727 row = center_rows[0] |
| 1728 if self.test_mode or self.galaxy_flag: | |
| 1729 assert(msnc.is_int(row, n1, n2-2)) | |
| 1690 if msnc.is_int(row, n1, n2-2): | 1730 if msnc.is_int(row, n1, n2-2): |
| 1691 if self.test_mode or self.galaxy_flag: | 1731 if self.test_mode or self.galaxy_flag: |
| 1692 assert(row is not None) | |
| 1693 use_row = 'yes' | 1732 use_row = 'yes' |
| 1694 else: | 1733 else: |
| 1695 msnc.quickImshow(center_stack[:,0,:], title=f'theta={theta_start}', aspect='auto') | 1734 msnc.quickImshow(center_stack[:,0,:], title=f'theta={theta_start}', aspect='auto') |
| 1696 use_row = pyip.inputYesNo('\nCurrent row index for lower center = ' | 1735 use_row = pyip.inputYesNo('\nCurrent row index for lower center = ' |
| 1697 f'{row}, use this value ([y]/n)? ', blank=True) | 1736 f'{row}, use this value ([y]/n)? ', blank=True) |
| 1712 if row == '': | 1751 if row == '': |
| 1713 row = n1 | 1752 row = n1 |
| 1714 if self.save_plots_only: | 1753 if self.save_plots_only: |
| 1715 msnc.clearFig(f'theta={theta_start}') | 1754 msnc.clearFig(f'theta={theta_start}') |
| 1716 # center_stack order: row,theta,column | 1755 # center_stack order: row,theta,column |
| 1756 if galaxy_param: | |
| 1757 recon_pngname = galaxy_param['recon_center_low'] | |
| 1758 else: | |
| 1759 recon_pngname = None | |
| 1717 center_offset = self._findCenterOnePlane(center_stack[row,:,:], row, thetas_deg, | 1760 center_offset = self._findCenterOnePlane(center_stack[row,:,:], row, thetas_deg, |
| 1718 eff_pixel_size, cross_sectional_dim, num_core=num_core, | 1761 eff_pixel_size, cross_sectional_dim, num_core=num_core, |
| 1719 recon_pngname=recon_low_pngname) | 1762 recon_pngname=recon_pngname, galaxy_param=galaxy_param) |
| 1763 logging.info(f'lower_center_offset = {center_offset:.2f} {type(center_offset)}') | |
| 1764 print(center_offset) | |
| 1720 | 1765 |
| 1721 # Update config and save to file | 1766 # Update config and save to file |
| 1722 find_center['row_bounds'] = [n1, n2] | 1767 find_center['row_bounds'] = [n1, n2] |
| 1723 find_center['lower_row'] = row | 1768 find_center['lower_row'] = row |
| 1724 find_center['lower_center_offset'] = center_offset | 1769 find_center['lower_center_offset'] = center_offset |
| 1727 | 1772 |
| 1728 # Upper row center | 1773 # Upper row center |
| 1729 use_row = 'no' | 1774 use_row = 'no' |
| 1730 use_center = 'no' | 1775 use_center = 'no' |
| 1731 row = center_rows[1] | 1776 row = center_rows[1] |
| 1777 if self.test_mode or self.galaxy_flag: | |
| 1778 assert(msnc.is_int(row, lower_row+1, n2-1)) | |
| 1732 if msnc.is_int(row, lower_row+1, n2-1): | 1779 if msnc.is_int(row, lower_row+1, n2-1): |
| 1733 if self.test_mode or self.galaxy_flag: | 1780 if self.test_mode or self.galaxy_flag: |
| 1734 assert(row is not None) | |
| 1735 use_row = 'yes' | 1781 use_row = 'yes' |
| 1736 else: | 1782 else: |
| 1737 msnc.quickImshow(center_stack[:,0,:], title=f'theta={theta_start}', aspect='auto') | 1783 msnc.quickImshow(center_stack[:,0,:], title=f'theta={theta_start}', aspect='auto') |
| 1738 use_row = pyip.inputYesNo('\nCurrent row index for upper center = ' | 1784 use_row = pyip.inputYesNo('\nCurrent row index for upper center = ' |
| 1739 f'{row}, use this value ([y]/n)? ', blank=True) | 1785 f'{row}, use this value ([y]/n)? ', blank=True) |
| 1754 if row == '': | 1800 if row == '': |
| 1755 row = n2-1 | 1801 row = n2-1 |
| 1756 if self.save_plots_only: | 1802 if self.save_plots_only: |
| 1757 msnc.clearFig(f'theta={theta_start}') | 1803 msnc.clearFig(f'theta={theta_start}') |
| 1758 # center_stack order: row,theta,column | 1804 # center_stack order: row,theta,column |
| 1805 if galaxy_param: | |
| 1806 recon_pngname = galaxy_param['recon_center_upp'] | |
| 1807 else: | |
| 1808 recon_pngname = None | |
| 1759 center_offset = self._findCenterOnePlane(center_stack[row,:,:], row, thetas_deg, | 1809 center_offset = self._findCenterOnePlane(center_stack[row,:,:], row, thetas_deg, |
| 1760 eff_pixel_size, cross_sectional_dim, num_core=num_core, | 1810 eff_pixel_size, cross_sectional_dim, num_core=num_core, |
| 1761 recon_pngname=recon_upp_pngname) | 1811 recon_pngname=recon_pngname, galaxy_param=galaxy_param) |
| 1762 logging.info(f'upper_center_offset = {center_offset}') | 1812 logging.info(f'upper_center_offset = {center_offset:.2f}') |
| 1763 del center_stack | 1813 del center_stack |
| 1764 | 1814 |
| 1765 # Update config and save to file | 1815 # Update config and save to file |
| 1766 find_center['upper_row'] = row | 1816 find_center['upper_row'] = row |
| 1767 find_center['upper_center_offset'] = center_offset | 1817 find_center['upper_center_offset'] = center_offset |
