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

253 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-05-05 18:53 +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 

121 

122class ObjectSelector(ThresholdSelector): 

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

124 

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

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

127 if self.plotLabelKey: 

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

129 return result 

130 

131 def setDefaults(self): 

132 super().setDefaults() 

133 self.op = "eq" 

134 self.threshold = 1 

135 self.plotLabelKey = "" 

136 self.vectorKey = "detect_isPrimary" 

137 

138 

139class ObjectClassTool(AnalysisTool): 

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

141 

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

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

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

145 """ 

146 

147 _plurals = { 

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

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

150 "any": "all", 

151 "galaxy": "galaxies", 

152 "star": "stars", 

153 } 

154 

155 selection_suffix = Field[str]( 

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

157 default="", 

158 ) 

159 selector_all = ConfigurableActionField[SelectorBase]( 

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

161 default=ObjectSelector, 

162 ) 

163 selector_galaxy = ConfigurableActionField[SelectorBase]( 

164 doc="The selector for target galaxies", 

165 default=GalaxySelector, 

166 ) 

167 selector_star = ConfigurableActionField[SelectorBase]( 

168 doc="The selector for target stars", 

169 default=StarSelector, 

170 ) 

171 

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

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

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

175 

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

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

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

179 

180 def get_class_name_plural(self, object_class: str): 

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

182 return self._plurals[object_class] 

183 

184 def get_class_type(self, object_class: str): 

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

186 match object_class: 

187 case "any": 

188 return self.type_any 

189 case "galaxy": 

190 return self.type_galaxies 

191 case "star": 

192 return self.type_stars 

193 

194 def get_classes(self): 

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

196 classes = [] 

197 if self.use_any: 

198 classes.append("any") 

199 if self.use_galaxies: 

200 classes.append("galaxy") 

201 if self.use_stars: 

202 classes.append("star") 

203 return classes 

204 

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

206 if selector_suffix is None: 

207 selector_suffix = self.selection_suffix 

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

209 

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

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

212 

213 def get_selector(self, object_class: str): 

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

215 match object_class: 

216 case "any": 

217 return self.selector_all 

218 case "galaxy": 

219 return self.selector_galaxy 

220 case "star": 

221 return self.selector_star 

222 

223 def finalize(self): 

224 for object_class in self.get_classes(): 

225 name_selector = self.get_name_attr_selector(object_class) 

226 selector = self.get_selector(object_class) 

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

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

229 # but to make several parallel selections. 

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

231 

232 def setDefaults(self): 

233 super().setDefaults() 

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

235 selector.vectorKey = "refExtendedness" 

236 

237 

238class MagnitudeTool(ObjectClassTool): 

239 """Compute magnitudes from flux columns. 

240 

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

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

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

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

245 

246 The fluxes_default attribute contains commonly used configurations for 

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

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

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

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

251 from simulations. 

252 

253 Notes 

254 ----- 

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

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

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

258 """ 

259 

260 fluxes_default = FluxesDefaultConfig( 

261 bulge_err=FluxConfig( 

262 key_flux="{band}_cModel_devFlux", 

263 key_flux_error="{band}_cModel_devFluxErr", 

264 name_flux="CModel Bulge", 

265 name_flux_short="bulge_cModel", 

266 ), 

267 cmodel_err=FluxConfig( 

268 key_flux="{band}_cModelFlux", 

269 key_flux_error="{band}_cModelFluxErr", 

270 name_flux="CModel", 

271 name_flux_short="cModel", 

272 ), 

273 disk_err=FluxConfig( 

274 key_flux="{band}_cModel_expFlux", 

275 key_flux_error="{band}_cModel_expFluxErr", 

276 name_flux="CModel Disk", 

277 name_flux_short="disk_cModel", 

278 ), 

279 gaap1p0_err=FluxConfig( 

280 key_flux="{band}_gaap1p0Flux", 

281 key_flux_error="{band}_gaap1p0FluxErr", 

282 name_flux='GAaP 1.0"', 

283 name_flux_short="gaap_1p0", 

284 ), 

285 kron_err=FluxConfig( 

286 key_flux="{band}_kronFlux", 

287 key_flux_error="{band}_kronFluxErr", 

288 name_flux="Kron", 

289 name_flux_short="kron", 

290 ), 

291 psf_err=FluxConfig( 

292 key_flux="{band}_psfFlux", 

293 key_flux_error="{band}_psfFluxErr", 

294 name_flux="PSF", 

295 name_flux_short="psf", 

296 ), 

297 ref_matched=FluxConfig( 

298 key_flux="refcat_flux_{band}", 

299 key_flux_error=None, 

300 name_flux="Reference", 

301 name_flux_short="ref", 

302 ), 

303 sersic_err=FluxConfig( 

304 key_flux="{band}_sersicFlux", 

305 key_flux_error="{band}_sersicFluxErr", 

306 name_flux="Sérsic", 

307 name_flux_short="sersic", 

308 ), 

309 ) 

310 

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

312 default={}, 

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

314 ) 

315 

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

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

318 

319 Parameters 

320 ---------- 

321 name 

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

323 config 

324 The configuration for the flux. 

325 band 

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

327 the parameterized band. 

328 

329 Returns 

330 ------- 

331 name 

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

333 """ 

334 if band is None: 

335 band = "{band}" 

336 else: 

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

338 key_flux = config.key_flux_band(band=band) 

339 name_flux = f"flux_{name}" 

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

341 if config.key_flux_error is not None: 

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

343 key_flux_err = config.key_flux_error_band(band=band) 

344 self._set_action( 

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

346 ) 

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

348 return name 

349 

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

351 attr = getattr(self, name_mag) 

352 if attr not in self.fluxes: 

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

354 return self.fluxes[attr] 

355 

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

357 self._set_flux_default(name_mag) 

358 attr = getattr(self, name_mag) 

359 key_mag = f"mag_{attr}" 

360 classes = self.get_classes() 

361 for object_class in classes: 

362 name_selector = self.get_name_attr_selector(object_class) 

363 self._set_action( 

364 self.process.filterActions, 

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

366 DownselectVector, 

367 vectorKey=key_mag, 

368 selector=VectorSelector(vectorKey=name_selector), 

369 ) 

370 

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

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

373 

374 Parameters 

375 ---------- 

376 target 

377 The ConfigurableActionStructField to set an attribute on. 

378 name 

379 The name of the attribute to set. 

380 action 

381 The action class to set the attribute to. 

382 args 

383 Arguments to pass when initialization the action. 

384 kwargs 

385 Keyword arguments to pass when initialization the action. 

386 """ 

387 if hasattr(target, name): 

388 attr = getattr(target, name) 

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

390 assert isinstance(attr, action) 

391 # Assert that the action's attributes are identical 

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

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

394 assert getattr(attr, key) == value 

395 else: 

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

397 

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

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

400 

401 Parameters 

402 ---------- 

403 attr 

404 The name of the attribute to set. 

405 band 

406 The name of the band to pass to _add_flux. 

407 name_mag 

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

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

410 """ 

411 name_mag_is_none = name_mag is None 

412 if name_mag_is_none: 

413 name_mag = getattr(self, attr) 

414 complete = name_mag in self.fluxes 

415 else: 

416 complete = hasattr(self, attr) 

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

418 if not complete: 

419 name_found = None 

420 drop_err = False 

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

422 if name_mag.endswith("_err"): 

423 if hasattr(self.fluxes_default, name_mag): 

424 name_found = name_mag 

425 else: 

426 if hasattr(self.fluxes_default, name_mag): 

427 name_found = name_mag 

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

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

430 name_found = f"{name_mag}_err" 

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

432 drop_err = True 

433 if name_found: 

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

435 # Other instances may use them 

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

437 # Ensure no unneeded error columns are loaded 

438 if drop_err: 

439 value.key_flux_error = None 

440 self.fluxes[name_found] = value 

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

442 else: 

443 raise RuntimeError( 

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

445 f" and no default configuration found" 

446 ) 

447 if name_mag_is_none and (name_mag != name_found): 

448 # Essentially appends _err to the name if needed 

449 setattr(self, attr, name_found) 

450 

451 def finalize(self): 

452 super().finalize() 

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

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

455 

456 

457class MagnitudeXTool(MagnitudeTool): 

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

459 

460 mag_x = Field[str]( 

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

462 ) 

463 

464 @property 

465 def config_mag_x(self): 

466 return self._config_mag() 

467 

468 def finalize(self): 

469 super().finalize() 

470 self._finalize_mag() 

471 

472 

473class SizeConfig(Config): 

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

475 

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

477 key_size = Field[str]( 

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

479 ) 

480 log10_size = Field[bool]( 

481 default=True, 

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

483 ) 

484 name_size = Field[str]( 

485 default="size", 

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

487 ) 

488 scale_size = Field[float]( 

489 default=0.2, 

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

491 ) 

492 unit_size = Field[str]( 

493 default="arcsec", 

494 doc="Unit for sizes.", 

495 ) 

496 

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

498 if self.log10_size: 

499 action = Log10Vector(actionA=action) 

500 return action 

501 

502 

503class SizeDefaultConfig(Config): 

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

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

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

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

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

509 

510 

511class MomentsConfig(Config): 

512 """Configuration for moment field suffixes.""" 

513 

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

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

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

517 

518 

519class SizeTool(ObjectClassTool): 

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

521 

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

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

524 read from. 

525 

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

527 support arbitrary sizes, like MagnitudeTool. 

528 """ 

529 

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

531 config_moments = ConfigField[MomentsConfig]( 

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

533 ) 

534 is_covariance = Field[bool]( 

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

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

537 default=True, 

538 ) 

539 sizes_default = SizeDefaultConfig( 

540 bulge=SizeConfig( 

541 key_size="{band}_cModel_dev_reff_major", name_size="CModel Bulge $R_{eff}$", has_moments=False 

542 ), 

543 disk=SizeConfig( 

544 key_size="{band}_cModel_exp_reff_major", name_size="CModel Disk $R_{eff}$", has_moments=False 

545 ), 

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

547 sersic=SizeConfig( 

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

549 ), 

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

551 ) 

552 size_type = ChoiceField[str]( 

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

554 allowed={ 

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

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

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

558 }, 

559 optional=False, 

560 ) 

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

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

563 default={}, 

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

565 ) 

566 

567 def _check_attr(self, name_size: str): 

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

569 attr = self.get_attr_name(name_size) 

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

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

572 

573 def _get_action_determinant(self, config): 

574 action = CalcMomentSize( 

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

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

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

578 is_covariance=self.is_covariance, 

579 ) 

580 return action 

581 

582 def _get_action_trace(self, config): 

583 action = CalcMomentSize( 

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

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

586 is_covariance=self.is_covariance, 

587 ) 

588 return action 

589 

590 def _get_action_single_column(self, config): 

591 action = LoadVector(vectorKey=config.key_size) 

592 return action 

593 

594 def get_attr_name(self, name_size): 

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

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

597 

598 def setDefaults(self): 

599 super().setDefaults() 

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

601 

602 def finalize(self): 

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

604 classes = self.get_classes() 

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

606 return 

607 super().finalize() 

608 if not self.size_y: 

609 raise ValueError("Must specify size_y") 

610 elif self.size_y not in self.sizes: 

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

612 self.sizes[self.size_y] = size_y 

613 else: 

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

615 

616 if self.size_type == "determinantRadius": 

617 get_action = self._get_action_determinant 

618 elif self.size_type == "traceRadius": 

619 get_action = self._get_action_trace 

620 elif self.size_type == "singleColumnSize": 

621 get_action = self._get_action_single_column 

622 else: 

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

624 

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

626 self._check_attr(name) 

627 action = config.modify_action( 

628 MultiplyVector( 

629 actionA=get_action(config=config), 

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

631 ) 

632 ) 

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

634 

635 attr = self.get_attr_name(self.size_y) 

636 classes = self.get_classes() 

637 for object_class in classes: 

638 setattr( 

639 self.process.filterActions, 

640 self.get_name_attr_values(object_class), 

641 DownselectVector( 

642 vectorKey=attr, 

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

644 ), 

645 )