Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PyKwalify uses deprecated load_module function in core._load_extensions() #202

Open
JoostJM opened this issue Jan 28, 2025 · 0 comments · May be fixed by #203
Open

PyKwalify uses deprecated load_module function in core._load_extensions() #202

JoostJM opened this issue Jan 28, 2025 · 0 comments · May be fixed by #203

Comments

@JoostJM
Copy link

JoostJM commented Jan 28, 2025

Environment

  • Python version: 3.10
  • PyKwalify version: 1.8.0

Steps to Reproduce

  • Validate any schema with additional extensions. Easiest to reproduce using PyRadiomics repo (which uses pykwalify). From the pyradiomics repo run python bin/testParams.py examples/exampleSettings/exampleCT.yaml. This won't fail, because the function is deprecated, but not removed.
  • Alternative, run pyradiomcis tests of example settings: from repo tests folder, run pytest test_exampleSettings.py. This fails because of the deprecation warning.

Schema

See Steps to reproduce. Schema: PyRadiomics Schema, with extensions

Schema:

# Parameters schema
name: Parameter schema
desc: This schema defines what arguments may be present in the parameters file that can be passed to the pyradiomics package.
type: map
mapping:
  setting: &settings
    type: map
    mapping:
      minimumROIDimensions:
        type: int
        range:
          min: 1
          max: 3
      minimumROISize:
        type: int
        range:
          min-ex: 0
      geometryTolerance:
        type: float
        range:
          min-ex: 0
      correctMask:
        type: bool
      additionalInfo:
        type: bool
      label:
        type: int
        range:
          min-ex: 0
      label_channel:
        type: int
        range:
          min: 0
      binWidth:
        type: float
        range:
          min-ex: 0
      binCount:
        type: int
        range:
          min-ex: 0
      normalize:
        type: bool
      normalizeScale:
        type: float
        range:
          min-ex: 0
      removeOutliers:
        type: float
        range:
          min-ex: 0
      resampledPixelSpacing:
        seq:
          - type: float
            range:
              min: 0
      interpolator:
        type: any
        func: checkInterpolator
      padDistance:
        type: int
        range:
          min: 0
      distances:
        seq:
          - type: int
            range:
              min-ex: 0
      force2D:
        type: bool
      force2Ddimension:
        type: int
        range:
          min: 0
          max: 2
      resegmentRange:
        seq:
          - type: float
      resegmentMode:
        type: str
        enum: ['absolute', 'relative', 'sigma']
      resegmentShape:
        type: bool
      preCrop:
        type: bool
      sigma:
        seq:
          - type: float
            range:
              min-ex: 0
      start_level:
        type: int
        range:
          min: 0
      level:
        type: int
        range:
          min-ex: 0
      wavelet:
        type: str
        func: checkWavelet
      gradientUseSpacing:
        type: bool
      lbp2DRadius:
        type: float
        range:
          min-ex: 0
      lbp2DSamples:
        type: int
        range:
          min: 1
      lbp2DMethod:
        type: str
        enum: ['default', 'ror', 'uniform', 'var']
      lbp3DLevels:
        type: int
        range:
          min: 1
      lbp3DIcosphereRadius:
        type: float
        range:
          min-ex: 0
      lbp3DIcosphereSubdivision:
        type: int
        range:
          min: 0
      voxelArrayShift:
        type: int
      symmetricalGLCM:
        type: bool
      weightingNorm:
        type: any
        func: checkWeighting
      gldm_a:
        type: int
        range:
          min: 0

  voxelSetting:
    type: map
    mapping:
      kernelRadius:
        type: int
        range:
          min-ex: 0
      maskedKernel:
        type: bool
      initValue:
        type: float
      voxelBatch:
        type: int
        range:
          min-ex: 0

  featureClass:
    type: map
    func: checkFeatureClass
    matching-rule: 'any'
    mapping:
      regex;(.+):
        type: any

  imageType:
    type: map
    func: checkImageType
    matching-rule: 'any'
    mapping:
       regex;(.+): *settings

Schema extensions:

import pywt
import six

from radiomics import getFeatureClasses, getImageTypes

featureClasses = getFeatureClasses()
imageTypes = getImageTypes()

def checkWavelet(value, rule_obj, path):
  if not isinstance(value, six.string_types):
    raise TypeError('Wavelet not expected type (str)')
  wavelist = pywt.wavelist()
  if value not in wavelist:
    raise ValueError('Wavelet "%s" not available in pyWavelets %s' % (value, wavelist))
  return True


def checkInterpolator(value, rule_obj, path):
  if value is None:
    return True
  if isinstance(value, six.string_types):
    enum = {'sitkNearestNeighbor',
            'sitkLinear',
            'sitkBSpline',
            'sitkGaussian',
            'sitkLabelGaussian',
            'sitkHammingWindowedSinc',
            'sitkCosineWindowedSinc',
            'sitkWelchWindowedSinc',
            'sitkLanczosWindowedSinc',
            'sitkBlackmanWindowedSinc'}
    if value not in enum:
      raise ValueError('Interpolator value "%s" not valid, possible values: %s' % (value, enum))
  elif isinstance(value, int):
    if value < 1 or value > 10:
      raise ValueError('Intepolator value %i, must be in range of [1-10]' % (value))
  else:
    raise TypeError('Interpolator not expected type (str or int)')
  return True


def checkWeighting(value, rule_obj, path):
  if value is None:
    return True
  elif isinstance(value, six.string_types):
    enum = ['euclidean', 'manhattan', 'infinity', 'no_weighting']
    if value not in enum:
      raise ValueError('WeightingNorm value "%s" not valid, possible values: %s' % (value, enum))
  else:
    raise TypeError('WeightingNorm not expected type (str or None)')
  return True


def checkFeatureClass(value, rule_obj, path):
  global featureClasses
  if value is None:
    raise TypeError('featureClass dictionary cannot be None value')
  for className, features in six.iteritems(value):
    if className not in featureClasses.keys():
      raise ValueError(
        'Feature Class %s is not recognized. Available feature classes are %s' % (className, list(featureClasses.keys())))
    if features is not None:
      if not isinstance(features, list):
        raise TypeError('Value of feature class %s not expected type (list)' % (className))
      unrecognizedFeatures = set(features) - set(featureClasses[className].getFeatureNames())
      if len(unrecognizedFeatures) > 0:
        raise ValueError('Feature Class %s contains unrecognized features: %s' % (className, str(unrecognizedFeatures)))

  return True


def checkImageType(value, rule_obj, path):
  global imageTypes
  if value is None:
    raise TypeError('imageType dictionary cannot be None value')

  for im_type in value:
    if im_type not in imageTypes:
      raise ValueError('Image Type %s is not recognized. Available image types are %s' %
                       (im_type, imageTypes))

  return True

Data

PyRadiomics Example Schema

imageType:
  Original: {}
  LoG:
    sigma: [1.0, 2.0, 3.0, 4.0, 5.0]  # If you include sigma values >5, remember to also increase the padDistance.
  Wavelet: {}

featureClass:
  # redundant Compactness 1, Compactness 2 an Spherical Disproportion features are disabled by default, they can be
  # enabled by specifying individual feature names (as is done for glcm) and including them in the list.
  shape:
  firstorder:
  glcm:  # Disable SumAverage by specifying all other GLCM features available
    - 'Autocorrelation'
    - 'JointAverage'
    - 'ClusterProminence'
    - 'ClusterShade'
    - 'ClusterTendency'
    - 'Contrast'
    - 'Correlation'
    - 'DifferenceAverage'
    - 'DifferenceEntropy'
    - 'DifferenceVariance'
    - 'JointEnergy'
    - 'JointEntropy'
    - 'Imc1'
    - 'Imc2'
    - 'Idm'
    - 'Idmn'
    - 'Id'
    - 'Idn'
    - 'InverseVariance'
    - 'MaximumProbability'
    - 'SumEntropy'
    - 'SumSquares'
  glrlm:
  glszm:
  gldm:

setting:
  # Normalization:
  # most likely not needed, CT gray values reflect absolute world values (HU) and should be comparable between scanners.
  # If analyzing using different scanners / vendors, check if the extracted features are correlated to the scanner used.
  # If so, consider enabling normalization by uncommenting settings below:
  #normalize: true
  #normalizeScale: 500  # This allows you to use more or less the same bin width.

  # Resampling:
  # Usual spacing for CT is often close to 1 or 2 mm, if very large slice thickness is used,
  # increase the resampled spacing.
  # On a side note: increasing the resampled spacing forces PyRadiomics to look at more coarse textures, which may or
  # may not increase accuracy and stability of your extracted features.
  interpolator: 'sitkBSpline'
  resampledPixelSpacing: [1, 1, 1]
  padDistance: 10  # Extra padding for large sigma valued LoG filtered images

  # Mask validation:
  # correctMask and geometryTolerance are not needed, as both image and mask are resampled, if you expect very small
  # masks, consider to enable a size constraint by uncommenting settings below:
  #minimumROIDimensions: 2
  #minimumROISize: 50

  # Image discretization:
  # The ideal number of bins is somewhere in the order of 16-128 bins. A possible way to define a good binwidt is to
  # extract firstorder:Range from the dataset to analyze, and choose a binwidth so, that range/binwidth remains approximately
  # in this range of bins.
  binWidth: 25

  # first order specific settings:
  voxelArrayShift: 1000  # Minimum value in HU is -1000, shift +1000 to prevent negative values from being squared.

  # Misc:
  # default label value. Labels can also be defined in the call to featureextractor.execute, as a commandline argument,
  # or in a column "Label" in the input csv (batchprocessing)
  label: 1

Expected Behavior

To run pykwalify without deprecation warnings. This is needed to allow PyTest to complete without errors.

Observed Behavior

Got a deprecation warning from the function in pykwalify core: core._load_extensions(), uses SourceFileLoader.load_module(), it suggests to use exec_module() instead.

@JoostJM JoostJM linked a pull request Jan 28, 2025 that will close this issue
JoostJM added a commit to JoostJM/pyradiomics that referenced this issue Jan 28, 2025
- Update the matrices baseline numpy arrays to newer version. Values are unchanged, matrices were simply loaded and saved again using `arr = numpy.load(<path>); numpy.save(<path>, arr);`.
- Ignore specific deprecation warning related to a deprecated function being used in PyKwalify. See also Grokzen/pykwalify#202 and Grokzen/pykwalify#203.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant