Coverage for python / lsst / analysis / tools / atools / genericBuild.py: 30%

257 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-30 09:27 +0000

1# This file is part of analysis_tools. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (https://www.lsst.org). 

6# See the COPYRIGHT file at the top-level directory of this distribution 

7# for details of code ownership. 

8# 

9# This program is free software: you can redistribute it and/or modify 

10# it under the terms of the GNU General Public License as published by 

11# the Free Software Foundation, either version 3 of the License, or 

12# (at your option) any later version. 

13# 

14# This program is distributed in the hope that it will be useful, 

15# but WITHOUT ANY WARRANTY; without even the implied warranty of 

16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

17# GNU General Public License for more details. 

18# 

19# You should have received a copy of the GNU General Public License 

20# along with this program. If not, see <https://www.gnu.org/licenses/>. 

21from __future__ import annotations 

22 

23__all__ = ( 

24 "ExtendednessTool", 

25 "FluxConfig", 

26 "MagnitudeTool", 

27 "MagnitudeXTool", 

28 "ObjectClassTool", 

29 "SizeConfig", 

30 "SizeTool", 

31) 

32 

33import copy 

34 

35from lsst.pex.config import ChoiceField, Config, ConfigDictField, ConfigField, Field 

36from lsst.pex.config.configurableActions import ConfigurableActionField, ConfigurableActionStructField 

37 

38from ..actions.vector import ( 

39 CalcMomentSize, 

40 ConstantValue, 

41 ConvertFluxToMag, 

42 DownselectVector, 

43 LoadVector, 

44 Log10Vector, 

45 MultiplyVector, 

46 VectorSelector, 

47) 

48from ..actions.vector.selectors import ( 

49 CoaddPlotFlagSelector, 

50 GalaxySelector, 

51 SelectorBase, 

52 StarSelector, 

53 ThresholdSelector, 

54 VisitPlotFlagSelector, 

55) 

56from ..interfaces import AnalysisTool, KeyedData, Vector, VectorAction 

57 

58 

59class ExtendednessTool(AnalysisTool): 

60 """Select (non-)extended sources in visit/coadd contexts.""" 

61 

62 extendedness = Field[str]( 

63 default="refExtendedness", 

64 doc="Extendedness field to select sub-samples with", 

65 ) 

66 

67 parameterizedBand = Field[bool]( 

68 default=True, 

69 doc="Does this AnalysisTool support band as a name parameter", 

70 ) 

71 

72 def coaddContext(self) -> None: 

73 self.prep.selectors.flagSelector = CoaddPlotFlagSelector() 

74 self.prep.selectors.flagSelector.bands = ["{band}"] 

75 

76 def visitContext(self) -> None: 

77 self.parameterizedBand = False 

78 self.prep.selectors.flagSelector = VisitPlotFlagSelector() 

79 

80 def setDefaults(self): 

81 super().setDefaults() 

82 # Select any finite extendedness (but still exclude NaNs) 

83 self.process.buildActions.allSelector = StarSelector( 

84 vectorKey=self.extendedness, extendedness_maximum=1.0 

85 ) 

86 self.process.buildActions.galaxySelector = GalaxySelector(vectorKey=self.extendedness) 

87 self.process.buildActions.starSelector = StarSelector(vectorKey=self.extendedness) 

88 

89 

90class FluxConfig(Config): 

91 """Configuration for a flux vector to be loaded and potentially plotted.""" 

92 

93 key_flux = Field[str](default=None, doc="Format of the flux field to convert to magnitudes with {band}.") 

94 key_flux_error = Field[str](default=None, doc="Format of the flux error field.", optional=True) 

95 name_flux_short = Field[str]( 

96 default=None, 

97 doc="Short name of the flux/magnitude algorithm/model to use in metric keys", 

98 ) 

99 name_flux = Field[str]( 

100 default=None, 

101 doc="Name of the flux/magnitude algorithm/model to use in plot labels.", 

102 ) 

103 

104 def key_flux_band(self, band: str): 

105 return self.key_flux.format(band=band) 

106 

107 def key_flux_error_band(self, band: str): 

108 return self.key_flux_error.format(band=band) 

109 

110 

111class FluxesDefaultConfig(Config): 

112 bulge_err = ConfigField[FluxConfig](doc="Bulge model magnitude with errors") 

113 cmodel_err = ConfigField[FluxConfig](doc="CModel total magnitude with errors") 

114 disk_err = ConfigField[FluxConfig](doc="Disk model magnitude with errors") 

115 gaap1p0_err = ConfigField[FluxConfig](doc="Gaap 1.0 arcsec aperture magnitude with errors") 

116 kron_err = ConfigField[FluxConfig](doc="Kron aperture magnitude with errors") 

117 psf_err = ConfigField[FluxConfig](doc="PSF model magnitude with errors") 

118 ref_matched = ConfigField[FluxConfig](doc="Reference catalog magnitude") 

119 sersic_err = ConfigField[FluxConfig](doc="Sersic total magnitude with errors") 

120 exponential_err = ConfigField[FluxConfig](doc="Exponential total magnitude with errors") 

121 

122 

123class ObjectSelector(ThresholdSelector): 

124 """A selector that selects primary objects from an object table.""" 

125 

126 def __call__(self, data: KeyedData, **kwargs) -> Vector: 

127 result = super().__call__(data=data, **kwargs) 

128 if self.plotLabelKey: 

129 self._addValueToPlotInfo("primary objects", **kwargs) 

130 return result 

131 

132 def setDefaults(self): 

133 super().setDefaults() 

134 self.op = "eq" 

135 self.threshold = 1 

136 self.plotLabelKey = "" 

137 self.vectorKey = "detect_isPrimary" 

138 

139 

140class ObjectClassTool(AnalysisTool): 

141 """Config for tools that compute metrics for multiple classes of object. 

142 

143 Class refers to e.g. the star-galaxy classification (including other 

144 types such as AGN), whereas type refers to the more generic (un)resolved 

145 status of the object. Variability may be included in the future. 

146 """ 

147 

148 _plurals = { 

149 # This is a bit of a hack to keep metric names consistent 

150 # They never changed from all to any, whereas scatterPlot did 

151 "any": "all", 

152 "galaxy": "galaxies", 

153 "star": "stars", 

154 } 

155 

156 selection_suffix = Field[str]( 

157 doc="Suffix to append to selector names to summarize selection criteria", 

158 default="", 

159 ) 

160 selector_all = ConfigurableActionField[SelectorBase]( 

161 doc="The selector for target objects of all types", 

162 default=ObjectSelector, 

163 ) 

164 selector_galaxy = ConfigurableActionField[SelectorBase]( 

165 doc="The selector for target galaxies", 

166 default=GalaxySelector, 

167 ) 

168 selector_star = ConfigurableActionField[SelectorBase]( 

169 doc="The selector for target stars", 

170 default=StarSelector, 

171 ) 

172 

173 type_any = Field[str](doc="The classification for any type", default="all") 

174 type_galaxies = Field[str](doc="The classification for galaxies", default="resolved") 

175 type_stars = Field[str](doc="The classification for galaxies", default="unresolved") 

176 

177 use_any = Field[bool](doc="Whether any (all types) be a used category of type", default=True) 

178 use_galaxies = Field[bool](doc="Whether galaxies be a used category of type", default=True) 

179 use_stars = Field[bool](doc="Whether stars should be a used category of types", default=True) 

180 

181 def get_class_name_plural(self, object_class: str): 

182 """Return the plural form of a class name.""" 

183 return self._plurals[object_class] 

184 

185 def get_class_type(self, object_class: str): 

186 """Return the type of a given class of objects.""" 

187 match object_class: 

188 case "any": 

189 return self.type_any 

190 case "galaxy": 

191 return self.type_galaxies 

192 case "star": 

193 return self.type_stars 

194 

195 def get_classes(self): 

196 """Return all of the classes to be used.""" 

197 classes = [] 

198 if self.use_any: 

199 classes.append("any") 

200 if self.use_galaxies: 

201 classes.append("galaxy") 

202 if self.use_stars: 

203 classes.append("star") 

204 return classes 

205 

206 def get_name_attr_selector(self, object_class: str, selector_suffix: str = None): 

207 if selector_suffix is None: 

208 selector_suffix = self.selection_suffix 

209 return f"selector{selector_suffix}_{self.get_class_name_plural(object_class)}" 

210 

211 def get_name_attr_values(self, object_class: str, prefix: str = "y"): 

212 return f"{prefix}{self.get_class_name_plural(object_class).capitalize()}" 

213 

214 def get_selector(self, object_class: str): 

215 """Get the selector for a given object class.""" 

216 match object_class: 

217 case "any": 

218 return self.selector_all 

219 case "galaxy": 

220 return self.selector_galaxy 

221 case "star": 

222 return self.selector_star 

223 

224 def finalize(self): 

225 for object_class in self.get_classes(): 

226 name_selector = self.get_name_attr_selector(object_class) 

227 selector = self.get_selector(object_class) 

228 # This is a build action because selectors in prep are applied 

229 # with the and operator. We're not using these to filter all rows 

230 # but to make several parallel selections. 

231 setattr(self.process.buildActions, name_selector, selector) 

232 

233 def setDefaults(self): 

234 super().setDefaults() 

235 for selector in (self.selector_galaxy, self.selector_star): 

236 selector.vectorKey = "refExtendedness" 

237 

238 

239class MagnitudeTool(ObjectClassTool): 

240 """Compute magnitudes from flux columns. 

241 

242 This tool is designed to make it easy to configure multiple fluxes that 

243 are then converted into magnitudes. For example, a plot might show one 

244 magnitude on the x-axis, a second on the y-axis, and plot statistics as a 

245 function of signal-to-noise from a third magnitude. 

246 

247 The fluxes_default attribute contains commonly used configurations for 

248 object tables. The "_err" suffix that a flux has an associated error 

249 column that is needed for some calculation; it can and should be omitted 

250 if the error column is unneeded. Some flux columns in reference catalogs 

251 may not have an error at all, such as injection catalogs or truth catalogs 

252 from simulations. 

253 

254 Notes 

255 ----- 

256 Any tool that reads in flux columns and converts them to magnitudes can 

257 derive from this class and use the _add_flux method to set the 

258 necessary build actions in their own finalize() methods. 

259 """ 

260 

261 fluxes_default = FluxesDefaultConfig( 

262 bulge_err=FluxConfig( 

263 key_flux="{band}_cModel_devFlux", 

264 key_flux_error="{band}_cModel_devFluxErr", 

265 name_flux="CModel Bulge", 

266 name_flux_short="bulge_cModel", 

267 ), 

268 cmodel_err=FluxConfig( 

269 key_flux="{band}_cModelFlux", 

270 key_flux_error="{band}_cModelFluxErr", 

271 name_flux="CModel", 

272 name_flux_short="cModel", 

273 ), 

274 disk_err=FluxConfig( 

275 key_flux="{band}_cModel_expFlux", 

276 key_flux_error="{band}_cModel_expFluxErr", 

277 name_flux="CModel Disk", 

278 name_flux_short="disk_cModel", 

279 ), 

280 exponential_err=FluxConfig( 

281 key_flux="{band}_exponentialFlux", 

282 key_flux_error="{band}_exponentialFluxErr", 

283 name_flux="Exponential", 

284 name_flux_short="exp", 

285 ), 

286 gaap1p0_err=FluxConfig( 

287 key_flux="{band}_gaap1p0Flux", 

288 key_flux_error="{band}_gaap1p0FluxErr", 

289 name_flux='GAaP 1.0"', 

290 name_flux_short="gaap_1p0", 

291 ), 

292 kron_err=FluxConfig( 

293 key_flux="{band}_kronFlux", 

294 key_flux_error="{band}_kronFluxErr", 

295 name_flux="Kron", 

296 name_flux_short="kron", 

297 ), 

298 psf_err=FluxConfig( 

299 key_flux="{band}_psfFlux", 

300 key_flux_error="{band}_psfFluxErr", 

301 name_flux="PSF", 

302 name_flux_short="psf", 

303 ), 

304 ref_matched=FluxConfig( 

305 key_flux="refcat_flux_{band}", 

306 key_flux_error=None, 

307 name_flux="Reference", 

308 name_flux_short="ref", 

309 ), 

310 sersic_err=FluxConfig( 

311 key_flux="{band}_sersicFlux", 

312 key_flux_error="{band}_sersicFluxErr", 

313 name_flux="Sérsic", 

314 name_flux_short="sersic", 

315 ), 

316 ) 

317 

318 fluxes = ConfigDictField[str, FluxConfig]( # type: ignore 

319 default={}, 

320 doc="Flux fields to convert to magnitudes", 

321 ) 

322 

323 def _add_flux(self, name: str, config: FluxConfig, band: str | None = None) -> str: 

324 """Add requisite buildActions for a given flux. 

325 

326 Parameters 

327 ---------- 

328 name 

329 The name of the flux, without "flux_" prefix. 

330 config 

331 The configuration for the flux. 

332 band 

333 The name of the band. Default "{band}" assumes the this band is 

334 the parameterized band. 

335 

336 Returns 

337 ------- 

338 name 

339 The name of the flux, suffixed by band if band is not None. 

340 """ 

341 if band is None: 

342 band = "{band}" 

343 else: 

344 name = f"{name}_{band}" 

345 key_flux = config.key_flux_band(band=band) 

346 name_flux = f"flux_{name}" 

347 self._set_action(self.process.buildActions, name_flux, LoadVector, vectorKey=key_flux) 

348 if config.key_flux_error is not None: 

349 # Pre-emptively loaded for e.g. future S/N calculations 

350 key_flux_err = config.key_flux_error_band(band=band) 

351 self._set_action( 

352 self.process.buildActions, f"flux_err_{name}", LoadVector, vectorKey=key_flux_err 

353 ) 

354 self._set_action(self.process.buildActions, f"mag_{name}", ConvertFluxToMag, vectorKey=key_flux) 

355 return name 

356 

357 def _config_mag(self, name_mag: str = "mag_x"): 

358 attr = getattr(self, name_mag) 

359 if attr not in self.fluxes: 

360 raise KeyError(f"self.{name_mag}={attr} not in {self.fluxes}; was finalize called?") 

361 return self.fluxes[attr] 

362 

363 def _finalize_mag(self, name_mag: str = "mag_x", prefix: str = "x"): 

364 self._set_flux_default(name_mag) 

365 attr = getattr(self, name_mag) 

366 key_mag = f"mag_{attr}" 

367 classes = self.get_classes() 

368 for object_class in classes: 

369 name_selector = self.get_name_attr_selector(object_class) 

370 self._set_action( 

371 self.process.filterActions, 

372 self.get_name_attr_values(object_class, prefix=prefix), 

373 DownselectVector, 

374 vectorKey=key_mag, 

375 selector=VectorSelector(vectorKey=name_selector), 

376 ) 

377 

378 def _set_action(self, target: ConfigurableActionStructField, name: str, action, *args, **kwargs): 

379 """Set an action attribute on a target tool's struct field. 

380 

381 Parameters 

382 ---------- 

383 target 

384 The ConfigurableActionStructField to set an attribute on. 

385 name 

386 The name of the attribute to set. 

387 action 

388 The action class to set the attribute to. 

389 args 

390 Arguments to pass when initialization the action. 

391 kwargs 

392 Keyword arguments to pass when initialization the action. 

393 """ 

394 if hasattr(target, name): 

395 attr = getattr(target, name) 

396 # Setting an attr to a different action is a logic error 

397 assert isinstance(attr, action) 

398 # Assert that the action's attributes are identical 

399 for key, value in kwargs.items(): 

400 if value.__class__.__module__ == "__builtin__": 

401 assert getattr(attr, key) == value 

402 else: 

403 setattr(target, name, action(*args, **kwargs)) 

404 

405 def _set_flux_default(self, attr: str, band: str | None = None, name_mag: str | None = None) -> str: 

406 """Set own config attr to appropriate string flux name. 

407 

408 Parameters 

409 ---------- 

410 attr 

411 The name of the attribute to set. 

412 band 

413 The name of the band to pass to _add_flux. 

414 name_mag 

415 The name of the magnitude to configure. If None, self must already 

416 have an attr, and name_mag is set to the attr's value. 

417 """ 

418 name_mag_is_none = name_mag is None 

419 if name_mag_is_none: 

420 name_mag = getattr(self, attr) 

421 if name_mag is None: 

422 raise RuntimeError( 

423 f"{self=}.{attr=} cannot be None. If this is a config field, it must be set." 

424 ) 

425 complete = name_mag in self.fluxes 

426 else: 

427 complete = hasattr(self, attr) 

428 # Do nothing if already set - may have been called 2+ times 

429 if not complete: 

430 name_found = None 

431 drop_err = False 

432 # Check if the name with errors is a configured default 

433 if name_mag.endswith("_err"): 

434 if hasattr(self.fluxes_default, name_mag): 

435 name_found = name_mag 

436 else: 

437 if hasattr(self.fluxes_default, name_mag): 

438 name_found = name_mag 

439 # Check if a config with errors exists but not without 

440 elif hasattr(self.fluxes_default, f"{name_mag}_err"): 

441 name_found = f"{name_mag}_err" 

442 # Don't load the errors - no _err suffix == unneeded 

443 drop_err = True 

444 if name_found: 

445 # Copy the config - we don't want to edit in place 

446 # Other instances may use them 

447 value = copy.copy(getattr(self.fluxes_default, name_found)) 

448 # Ensure no unneeded error columns are loaded 

449 if drop_err: 

450 value.key_flux_error = None 

451 self.fluxes[name_found] = value 

452 name_found = self._add_flux(name=name_found, config=value, band=band) 

453 else: 

454 raise RuntimeError( 

455 f"flux={name_mag} not defined in self.fluxes={self.fluxes}" 

456 f" and no default configuration found" 

457 ) 

458 if name_mag_is_none and (name_mag != name_found): 

459 # Essentially appends _err to the name if needed 

460 setattr(self, attr, name_found) 

461 

462 def finalize(self): 

463 super().finalize() 

464 for key, config in self.fluxes.items(): 

465 self._add_flux(name=key, config=config) 

466 

467 

468class MagnitudeXTool(MagnitudeTool): 

469 """A Tool for metrics/plots with a magnitude as the dependent variable.""" 

470 

471 mag_x = Field[str]( 

472 doc="Flux (magnitude) FluxConfig key (in self.fluxes) to bin metrics by or plot on x-axis", 

473 ) 

474 

475 @property 

476 def config_mag_x(self): 

477 return self._config_mag() 

478 

479 def finalize(self): 

480 super().finalize() 

481 self._finalize_mag() 

482 

483 

484class SizeConfig(Config): 

485 """Configuration for size vector(s) to be loaded and possibly plotted.""" 

486 

487 has_moments = Field[bool](doc="Whether this size measure is stored as 2D moments.", default=True) 

488 key_size = Field[str]( 

489 doc="Size column(s) to compute/plot, including moment suffix as {suffix}.", 

490 ) 

491 log10_size = Field[bool]( 

492 default=True, 

493 doc="Whether to compute/plot)log10 of the sizes.", 

494 ) 

495 name_size = Field[str]( 

496 default="size", 

497 doc="Name of the size (e.g. for axis labels).", 

498 ) 

499 scale_size = Field[float]( 

500 default=1.0, 

501 doc="Factor to scale sizes (multiply) by.", 

502 ) 

503 unit_size = Field[str]( 

504 default="arcsec", 

505 doc="Unit for sizes.", 

506 ) 

507 

508 def modify_action(self, action: VectorAction) -> VectorAction: 

509 if self.log10_size: 

510 action = Log10Vector(actionA=action) 

511 return action 

512 

513 

514class SizeDefaultConfig(Config): 

515 bulge = ConfigField[SizeConfig](doc="Bulge model size config.") 

516 disk = ConfigField[SizeConfig](doc="Disk model size config.") 

517 exponential = ConfigField[SizeConfig](doc="Exponential model effective radius config") 

518 moments = ConfigField[SizeConfig](doc="Second moments size config.") 

519 sersic = ConfigField[SizeConfig](doc="Sersic model effective radius config") 

520 shape_slot = ConfigField[SizeConfig](doc="Shape slot size config.") 

521 

522 

523class MomentsConfig(Config): 

524 """Configuration for moment field suffixes.""" 

525 

526 xx = Field[str](doc="Suffix for the x/xx moments.", default="xx") 

527 xy = Field[str](doc="Suffix for the rho value/xy moments.", default="xy") 

528 yy = Field[str](doc="Suffix for the y/yy moments.", default="yy") 

529 

530 

531class SizeTool(ObjectClassTool): 

532 """Compute various object size definitions in linear or log space. 

533 

534 This tool is designed to make it easy to configure a size based on the 

535 definition of the size and the configuration of the columns that it is 

536 read from. 

537 

538 It currently only supports a single size but may be refactored to 

539 support arbitrary sizes, like MagnitudeTool. 

540 """ 

541 

542 attr_prefix = Field[str](doc="Prefix to prepend to size names as attrs", default="size_", optional=False) 

543 config_moments = ConfigField[MomentsConfig]( 

544 doc="Configuration for moment field names", default=MomentsConfig 

545 ) 

546 is_covariance = Field[bool]( 

547 doc="Whether this size has multiple fields as for a covariance matrix." 

548 " If False, the XX/YY/XY terms are instead assumed to map to sigma_x/sigma_y/rho.", 

549 default=True, 

550 ) 

551 sizes_default = SizeDefaultConfig( 

552 bulge=SizeConfig( 

553 key_size="{band}_cModel_dev_reff_major", 

554 name_size="CModel Bulge $R_{eff,major}$", 

555 has_moments=False, 

556 ), 

557 disk=SizeConfig( 

558 key_size="{band}_cModel_exp_reff_major", 

559 name_size="CModel Disk $R_{eff,major}$", 

560 has_moments=False, 

561 ), 

562 exponential=SizeConfig( 

563 key_size="exponential_reff_major", name_size="Exponential $R_{eff,major}$", has_moments=False 

564 ), 

565 moments=SizeConfig(key_size="{band}_i{suffix}", name_size="Second moment radius"), 

566 sersic=SizeConfig( 

567 key_size="sersic_reff_major", name_size="Sérsic $R_{eff,major}$", has_moments=False 

568 ), 

569 shape_slot=SizeConfig(key_size="shape_{suffix}", name_size="Shape slot radius"), 

570 ) 

571 size_type = ChoiceField[str]( 

572 doc="The type of size to calculate", 

573 allowed={ 

574 "determinantRadius": "The (matrix) determinant radius from x/y moments.", 

575 "traceRadius": "The (matrix) trace radius from x/y moments.", 

576 "singleColumnSize": "A pre-computed size from a single column.", 

577 }, 

578 optional=False, 

579 ) 

580 size_y = Field[str](default=None, doc="Name of size field to plot on y axis.") 

581 sizes = ConfigDictField[str, SizeConfig]( # type: ignore 

582 default={}, 

583 doc="Size fields to add to build actions", 

584 ) 

585 

586 def _check_attr(self, name_size: str): 

587 """Check if a buildAction has already been set.""" 

588 attr = self.get_attr_name(name_size) 

589 if hasattr(self.process.buildActions, attr): 

590 raise RuntimeError(f"Can't re-set size build action with already-used {attr=} from {name_size=}") 

591 

592 def _get_action_determinant(self, config): 

593 action = CalcMomentSize( 

594 colXx=config.key_size.format(suffix=self.config_moments.xx), 

595 colYy=config.key_size.format(suffix=self.config_moments.yy), 

596 colXy=config.key_size.format(suffix=self.config_moments.xy), 

597 is_covariance=self.is_covariance, 

598 ) 

599 return action 

600 

601 def _get_action_trace(self, config): 

602 action = CalcMomentSize( 

603 colXx=config.key_size.format(suffix=self.config_moments.xx), 

604 colYy=config.key_size.format(suffix=self.config_moments.yy), 

605 is_covariance=self.is_covariance, 

606 ) 

607 return action 

608 

609 def _get_action_single_column(self, config): 

610 action = LoadVector(vectorKey=config.key_size) 

611 return action 

612 

613 def get_attr_name(self, name_size): 

614 """Return the build action attribute for a size of a given name.""" 

615 return f"{self.attr_prefix}{name_size}" 

616 

617 def setDefaults(self): 

618 super().setDefaults() 

619 self.produce.plot.legendLocation = "lower left" 

620 

621 def finalize(self): 

622 # A lazy check for whether finalize has already been called 

623 classes = self.get_classes() 

624 if hasattr(self.process.filterActions, self.get_name_attr_values(classes[0])): 

625 return 

626 super().finalize() 

627 if not self.size_y: 

628 raise ValueError("Must specify size_y") 

629 elif self.size_y not in self.sizes: 

630 if size_y := getattr(self.sizes_default, self.size_y, None): 

631 self.sizes[self.size_y] = size_y 

632 else: 

633 raise RuntimeError(f"{self.size_y=} not found in {self.sizes=} or {self.sizes_default=}") 

634 

635 if self.size_type == "determinantRadius": 

636 get_action = self._get_action_determinant 

637 elif self.size_type == "traceRadius": 

638 get_action = self._get_action_trace 

639 elif self.size_type == "singleColumnSize": 

640 get_action = self._get_action_single_column 

641 else: 

642 raise ValueError(f"Unsupported {self.size_type=}") 

643 

644 for name, config in self.sizes.items(): 

645 self._check_attr(name) 

646 action = config.modify_action( 

647 MultiplyVector( 

648 actionA=get_action(config=config), 

649 actionB=ConstantValue(value=config.scale_size), 

650 ) 

651 ) 

652 setattr(self.process.buildActions, self.get_attr_name(name), action) 

653 

654 attr = self.get_attr_name(self.size_y) 

655 classes = self.get_classes() 

656 for object_class in classes: 

657 setattr( 

658 self.process.filterActions, 

659 self.get_name_attr_values(object_class), 

660 DownselectVector( 

661 vectorKey=attr, 

662 selector=VectorSelector(vectorKey=self.get_name_attr_selector(object_class)), 

663 ), 

664 )