Issue with 'NoneType' for Crop Pollination Model

What is the issue or question you have?

I am trying to run the crop pollination model. My issue is that the model fails complete due to ‘NoneType’ values in the raster (.tif) for my LULC maps when mapped to my correspondence of the LULC and biophysical table. I have struggled to resolve this for a few days after consulting a few colleagues as well as ChatGPT.

Specifically, I get the following error message:

ERROR:root:Error running InVEST pollination:
Traceback (most recent call last):
  File "c:\Users\ryanm\mambaforge\envs\teem_devstack\Lib\site-packages\natcap\invest\pollination.py", line 767, in execute
    alpha_kernel_raster_task = alpha_kernel_map[kernel_path]
                               ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^
KeyError: 'C:/Users/ryanm/Desktop/temp/pollinator_maps_invest\\intermediate_outputs\\kernel_180000.000000_UGA_2010.tif'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<ipython-input-4-d8f523ebc967>", line 54, in <module>
    natcap.invest.pollination.execute(args)
  File "c:\Users\ryanm\mambaforge\envs\teem_devstack\Lib\site-packages\natcap\invest\pollination.py", line 769, in execute
    alpha_kernel_raster_task = task_graph.add_task(
                               ^^^^^^^^^^^^^^^^^^^^
  File "c:\Users\ryanm\mambaforge\envs\teem_devstack\Lib\site-packages\taskgraph\Task.py", line 674, in add_task
    new_task._call()
  File "c:\Users\ryanm\mambaforge\envs\teem_devstack\Lib\site-packages\taskgraph\Task.py", line 1093, in _call
    payload = self._func(*self._args, **self._kwargs)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "c:\Users\ryanm\mambaforge\envs\teem_devstack\Lib\site-packages\pygeoprocessing\kernels.py", line 133, in exponential_decay_kernel
    create_distance_decay_kernel(
  File "c:\Users\ryanm\mambaforge\envs\teem_devstack\Lib\site-packages\pygeoprocessing\kernels.py", line 254, in create_distance_decay_kernel
    kernel_band = kernel_dataset.GetRasterBand(1)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'GetRasterBand'
DEBUG:taskgraph.Task:Invoking terminate. already terminated? True

What do you expect to happen?

I expect the following python script to run. This should translate LULC maps using the biophysical table and guide tables for crop pollination by pollinator species into pollinator abundance and supply raster maps (.tif) that I can then merge with my other data to assess changes in pollinator abundance spatiallly.

"""
Purpose: To calculate pollinator abundance and supply at high spatial resolution given land-use cover and biophysical tables. 
This produces a pollination abundance and supply raster data set for each country for each year in the data. 
This additionally will break down for each type of pollinator identified in the biophysical table.
"""

# Dependencies
import natcap.invest.pollination
import natcap.invest.utils
import os
import logging
logging.basicConfig(level=logging.DEBUG)


# Input Agrument for Data 
args = {
    # Determine the folder for outputs.
    'workspace_dir': 'C:/Users/ryanm/Desktop/temp/pollinator_maps_invest',
    # Farmer specific plots. If added, this would make values specific to farmer plots. 
    'farm_vector_path': '',
    # Translation of pollination abundance given the lulc
    'guild_table_path': 'C:/Users/ryanm/Desktop/temp/pollination_guild_table.csv',
    # Determines requirements for each species of pollinator
    'landcover_biophysical_table_path': "C:/Users/ryanm/Desktop/temp/pollination_lulc_biophysical_table.csv",
    'n_workers': '-1',
    'results_suffix': '',
}

# Sample countries and years
# sample_countries = ['ETH', 'NER', 'NGA', 'UGA', 'TZA', 'MWI']
# sample_years = range(2010, 2021)

# TEST:
sample_countries = ['UGA']
sample_years = [2010]

# Loop through each country and year
for country in sample_countries:
    for year in sample_years:
        # Update the LULC raster path dynamically for each country and year
        lulc_raster_path = f'C:/Users/ryanm/Desktop/temp/lulc_cropped/{country}/lulc_esa_{year}.tif'

        # Ensure the LULC raster file exists for the current combination of country and year
        if os.path.exists(lulc_raster_path):
            # Update the 'landcover_raster_path' in the arguments dictionary
            args['landcover_raster_path'] = lulc_raster_path

            # Optionally, you could add a results suffix for each country-year combination
            args['results_suffix'] = f'_{country}_{year}'

            # Execute the InVEST pollination model for the current country and year
            print(f"Running InVEST Pollination for {country} in {year}...")
            try:
                natcap.invest.pollination.execute(args)
            except Exception as e:
                logging.error("Error running InVEST pollination:", exc_info=True)
        else:
            print(f"Warning: LULC raster file for {country} in {year} does not exist.")

What have you tried so far?

I took ESA CCI data for LULC maps as my base data. This has metadata describing the LULC codes. It labels no data as zero. So in the biophysical table I create a row to capture this value. Putting the raster maps into QGIS, I notice that it determines no data as 255. So I add this value as well. Despite this, the script fails because it is reading some grid values as ‘NoneType’. If I add NoneType to the biophysical table to give it a correspondence, the script fails stating that the lulc codes are no longer integers and are instead string values (which makes sense). I asked two colleagues about this, and we unsuccessfully troubleshooted for a couple of days. I then asked ChatGPT for a solution using my code and the error messages. This suggested I check the paths to the lulc maps (they read in fine), the path to my intermediate files (that exists and is okay), that I check my biophysical table (and as mentioned that can’t hold NoneType as a correspondence value). So this was a dead end. My suspicion is that intermediate_outputs\\kernel_180000.000000_UGA_2010.tif is the key to my answer but I can’t figure out how to resolve it.

Welcome @ryan , thanks for posting,

I agree. This error: AttributeError: 'NoneType' object has no attribute 'GetRasterBand' usually means that the raster does not actually exist. So something silently went wrong when creating that kernel raster. The function responsible for creating that raster is pygeoprocessing.kernels.create_distance_decay_kernel and it’s not clear to me what could have gone wrong.

One thing you can try in your script is to enable GDAL exceptions, so that a RuntimeError is raised when something goes wrong. That may help pinpoint the problem. At the top of your script, you could add,

from osgeo import gdal

gdal.UseExceptions()

And also make sure you’re using a recent release of pygeoprocessing. I recommend installing the latest version available from conda-forge.

If you try this, let us know what happens.

Thank you @dave !

I updated my conda version conda 22.11.1. I uninstalled and reinstalled pygeoprocessing pygeoprocessing 2.4.6 py311h9b10771_0 conda-forge.

Upon rerunning, I encounter a runtime error (I believe not enough disk space to allocate for the operation). What is quite strange is that it is requesting 12.96 TB of space (way too large for one country for one year) and says I already allocated 1.96 TB (my computer only has 256 GB). So I find this a bit perplexing:

INFO:natcap.invest.pollination:Checking to make sure guild table has all expected headers
Running InVEST Pollination for MWI in 2010...
DEBUG:taskgraph.Task:file:///D:/pollinator-pesticide/raw-data/pollinators/pollinator_maps_invest/taskgraph_cache/taskgraph_data.db?mode=ro exists: True
DEBUG:taskgraph.Task:_call check if precalculated reclassify_to_substrate_cavity (0)
DEBUG:taskgraph.Task:file_stat_list: [('d:\\pollinator-pesticide\\raw-data\\pollinators\\lulc_cropped\\mwi\\lulc_esa_2010.tif', '3243922::1733265286000000000::d:\\pollinator-pesticide\\raw-data\\pollinators\\lulc_cropped\\mwi\\lulc_esa_2010.tif')]
DEBUG:taskgraph.Task:other_arguments: [[(None, 1), {255: 0.0, 0: 0.0, 10: 0.1, 11: 0.1, 12: 0.1, 20: 0.1, 30: 0.1, 40: 0.6, 50: 0.6, 60: 0.6, 61: 0.6, 62: 0.6, 70: 0.6, 71: 0.6, 72: 0.6, 80: 0.6, 81: 0.6, 82: 0.6, 90: 0.6, 100: 0.6, 110: 0.1, 120: 0.1, 121: 0.1, 122: 0.1, 130: 0.1, 140: 0.3, 150: 0.3, 151: 0.3, 152: 0.3, 153: 0.3, 160: 0.6, 170: 0.6, 180: 0.3, 190: 0.3, 200: 0.0, 201: 0.0, 202: 0.0, 210: 0.0, 220: 0.0}, 'd:\\pollinator-pesticide\\scripts\\01-clean\\in_target_path_list', 6, -1, {'raster_name': 'd:\\pollinator-pesticide\\scripts\\01-clean\\lulc', 'column_name': 'd:\\pollinator-pesticide\\scripts\\01-clean\\lucode', 'table_name': 'd:\\pollinator-pesticide\\scripts\\01-clean\\biophysical'}], {}]
DEBUG:taskgraph.Task:file:///D:/pollinator-pesticide/raw-data/pollinators/pollinator_maps_invest/taskgraph_cache/taskgraph_data.db?mode=ro exists: True
DEBUG:taskgraph.Task:not precalculated, Task hash does not exist (reclassify_to_substrate_cavity (0))
DEBUG:taskgraph.Task:is_precalculated full task info: Task object 1399364392528:

{'exception_object': None,
 'ignore_directories': True,
 'ignore_path_list': [],
 'priority': 0,
 'self._reexecution_info': {'args_clean': [('d:\\pollinator-pesticide\\raw-data\\pollinators\\lulc_cropped\\mwi\\lulc_esa_2010.tif',
                                            1),
                                           {0: 0.0,
                                            10: 0.1,
                                            11: 0.1,
                                            12: 0.1,
                                            20: 0.1,
                                            30: 0.1,
                                            40: 0.6,
                                            50: 0.6,
                                            60: 0.6,
                                            61: 0.6,
                                            62: 0.6,
...
    return _gdal.Driver_Create(self, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
RuntimeError: kernel_180000.000000_MWI_2010.tif: Free disk space available is 1955553476608 bytes, whereas 12962764947456 are at least necessary. You can disable this check by defining the CHECK_DISK_FREE_SPACE configuration option to FALSE.
DEBUG:taskgraph.Task:Invoking terminate. already terminated? True

In intermediate outputs, it does appear to create the .tif for the intermediate values of foraged flower index, habitat nesting index, nesting substrate index, and relative floral abundance. So it would appear to me that it is failing at the final step of calculating abundance and supply using the intermediate raster maps that it has generated.

I tried this on a couple different year/country pairs, checked the values out in QGIS (inputs look fine), and still not sure what is happening that would throw this runtime error for disk space.

My next step is to restart the computer and try to start fresh. Maybe you or someone else can identify my mistake.

Thanks!

@ryan , cool, thanks for the update! Things like this can happen sometimes when there are problems with incorrectly defined coordinate systems on some input datasets, or distance parameters using the wrong units. Things like that can lead to a raster being created that is wildly larger than you might expect.

In kernel_180000.000000_MWI_2010.tif 180,000 is the alpha value, used as a kernel distance parameter. alpha is calculated as,

alpha = (
            scenario_variables['alpha_value'][species] /
            landcover_mean_pixel_size)

Does 180,000 make sense? What are the coordinate system units of the landcover raster? And what are the alpha values in the table?

Thanks @dave ,

I clip the ESA CCI maps to country boundaries (GADM) using the following code with gdal.Warp. I notice that you may be correct about a weird CRS as I am not asserting the CRS be the same between my country vector data and the LULC raster data. So I have incorporated that into the script I use to clip my LULC to country boundaries and re-run it. Before, I was able to produce clipped LULC maps at the country level for each year (the input LULC data for the crop pollination code above). Now I am getting a new error that I am trying to resolve. It seems that once I re-project the geopackage, it now no longer recognizes the ADM_0 layer that distinguishes country code (messed up the schema somehow). I put the error first and then provide the full script.

Error:

Reprojecting vector to raster CRS...
Reprojected vector file already exists: D:/pollinator-pesticide\raw-data\borders\gadm_410-levels_reprojected.gpkg. Skipping reprojection.
Layer names in the vector file: ['ADM_0']
Fields in the ADM_0 layer: []
ERROR 1: sqlite3_prepare_v2(SELECT Count(*) FROM "ADM_0" WHERE GID_0 = 'TZA') failed: no such column: GID_0
Warning: Country TZA not found in the ADM_0 layer.

Code:

import os
from osgeo import gdal, ogr, osr
import pygeoprocessing as pygeo 

# Sample: Countries 
sample_countries = ['ETH', 'NER', 'NGA', 'UGA', 'TZA', 'MWI']

# Define user and project directories
user_dir = os.path.expanduser('~')
cur_dir = "D:/pollinator-pesticide"
os.makedirs(cur_dir, exist_ok = True)

# Define paths to country vector files
vector_path = os.path.join(cur_dir, 'raw-data', 'borders', 'gadm_410-levels.gpkg')

# Directory containing the raster time series
raster_dir = os.path.join(cur_dir, 'raw-data', 'pollinators', 'lulc_esa')

# Get CRS: Raster data
def get_raster_crs(raster_path):
    """
    Returns the CRS of the input raster.
    """
    raster_ds = gdal.Open(raster_path)
    raster_srs = osr.SpatialReference(wkt=raster_ds.GetProjectionRef())
    return raster_srs

# Get CRS: Country vector data
def get_vector_crs(vector_path):
    """
    Returns the CRS of the input vector.
    """
    vector_ds = ogr.Open(vector_path)
    layer = vector_ds.GetLayer()
    vector_srs = layer.GetSpatialRef()
    return vector_srs

# Check that country file exists
def get_layer_names(vector_path):
    vector_ds = ogr.Open(vector_path)
    layer_names = [vector_ds.GetLayer(i).GetName() for i in range(vector_ds.GetLayerCount())]
    return layer_names

# Adjust layer field if country not found
def get_layer_fields(vector_path, layer_name):
    vector_ds = ogr.Open(vector_path)
    layer = vector_ds.GetLayerByName(layer_name)
    fields = [field.name for field in layer.schema]
    return fields

# Check country layer is in GADM geopackage
def check_country_in_layer(vector_path, layer_name, country_code):
    vector_ds = ogr.Open(vector_path)
    layer = vector_ds.GetLayerByName(layer_name)
    layer.SetAttributeFilter(f"GID_0 = '{country_code}'")
    count = layer.GetFeatureCount()
    return count > 0


# Reproject the country vector CRS to the lulc raster CRS
def reproject_vector_to_raster_crs(vector_path, raster_crs, output_vector_path):
    """
    Reprojects the input vector to the target CRS (raster CRS).

    If the reprojected vector already exists, it simply skips the reprojection.
    """
    # Check if the reprojected vector file already exists
    if os.path.exists(output_vector_path):
        print(f"Reprojected vector file already exists: {output_vector_path}. Skipping reprojection.")
        return  # Skip reprojection if the file already exists

    vector_ds = ogr.Open(vector_path)
    layer = vector_ds.GetLayer()

    # Create a reprojected output file
    driver = ogr.GetDriverByName('GPKG')
    output_ds = driver.CreateDataSource(output_vector_path)
    
    # Create the layer with the new CRS
    layer_definition = layer.GetLayerDefn()
    output_layer = output_ds.CreateLayer(layer_definition.GetName(), srs=raster_crs)
    
    # Get spatial reference of the vector layer
    layer_srs = layer.GetSpatialRef()

    # Reproject the vector layer to the raster CRS
    ogr2ogr_command = f'ogr2ogr -s_srs {layer_srs.ExportToWkt()} -t_srs {raster_crs.ExportToWkt()} -lco SPATIAL_INDEX=YES {output_vector_path} {vector_path} -overwrite -f "GPKG"'
    os.system(ogr2ogr_command)

    print(f"Reprojection completed: {output_vector_path}")


# Check that Re-projection did not drop layers (country ISO)
def check_column_exists(vector_path, layer_name, column_name):
    """
    Check if a column exists in the layer.
    """
    vector_ds = ogr.Open(vector_path)
    layer = vector_ds.GetLayerByName(layer_name)
    fields = [field.name for field in layer.schema]
    print(f"Fields in layer '{layer_name}': {fields}")
    return column_name in fields




# Clip the lulc raster data to the country vector borders
def clip_raster_to_vector(raster_path, vector_path, output_raster_path, country):
    """
    Clips the input raster to the extent of the vector shapefile.

    Parameters:
    - raster_path: Path to the input raster.
    - vector_path: Path to the vector file for clipping.
    - output_raster_path: Path to save the clipped raster.
    """
    # Get the CRS of the raster and the vector
    raster_crs = get_raster_crs(raster_path)
    vector_crs = get_vector_crs(vector_path)

    # Reproject the vector to match the raster's CRS if needed
    if raster_crs.IsSame(vector_crs) == 0:  # CRS are different
        print(f"Reprojecting vector to raster CRS...")
        reprojected_vector_path = vector_path.replace('.gpkg', '_reprojected.gpkg')
        reproject_vector_to_raster_crs(vector_path, raster_crs, reprojected_vector_path)
        vector_path = reprojected_vector_path  # Use the reprojected vector
    else:
        print("Vector CRS matches raster CRS. No reprojection needed.")

     # Check if the column exists in the reprojected vector
    if not check_column_exists(reprojected_vector_path, "ADM_0", "GID_0"):
        print("Warning: 'GID_0' column is missing after reprojection.")
        
    # Get NoData value from input raster
    nodata_val = pygeo.geoprocessing.get_raster_info(raster_path)['nodata'][0]

    # Check if the country exists in the vector layer
    layer_names = get_layer_names(vector_path)
    print(f"Layer names in the vector file: {layer_names}")

    # Assuming "ADM_0" is the correct layer name; adjust if necessary
    layer_name = "ADM_0"
    fields = get_layer_fields(vector_path, layer_name)
    print(f"Fields in the {layer_name} layer: {fields}")

    # Check if the country exists in the vector layer
    if not check_country_in_layer(vector_path, layer_name, country):
        print(f"Warning: Country {country} not found in the {layer_name} layer.")
        return
    
    # Perform clipping
    gdal.Warp(output_raster_path, raster_path, cutlineDSName=vector_path,
            cropToCutline=True,
            cutlineLayer='ADM_0',
            cutlineWhere=f"GID_0 = '{country}'",
            srcNodata=nodata_val, 
            dstNodata=nodata_val)
    print(f"Clipping completed: {output_raster_path}")

# Process each raster file in the directory
for raster_file in os.listdir(raster_dir):
    if raster_file.startswith('lulc_esa_') and raster_file.endswith('.tif'):
        raster_path = os.path.join(raster_dir, raster_file)

        # Create the new naming structure for clipped and reclassified rasters
        year = raster_file.split('_')[-1].split('.')[0]
        for country in sample_countries:
            clipped_raster_path = os.path.join(cur_dir, 'raw-data', 'pollinators', 'lulc_cropped', f"{country}" , f"lulc_esa_{year}.tif")

            # Clip the raster if it has not been clipped yet
            if not os.path.exists(clipped_raster_path):
                clip_raster_to_vector(raster_path, vector_path, clipped_raster_path, country)

ChatGPT Solution

Problem Breakdown

  • Vector Reprojection: During the reprojection process, the attribute fields (including GID_0) may not be transferred correctly. The ogr2ogr command is used for reprojection, but it seems that the attributes may be dropped during the process.
  • Missing GID_0 column: After reprojection, the ADM_0 layer does not have the GID_0 column, which is causing the failure when trying to clip based on that column.

I have tired a couple iterations of the code following that advice, but I still am coming up with the SQL query error that the ADM_0 layer is missing after the re-projection.

For the alpha for each species, I am attaching the guide table that I am using
pollination_guild_table.csv (296 Bytes). Alpha ranges from 500 to 1500. So 180,000 alpha is way outside the bounds of what I am expecting. ESA CCI resolution is 300-meters, so I would expect something in the 10s not 100s of thousands.

I would double-check that the original LULC raster has the correctly defined coordinate system. For example, it could be defined in the metadata as using a system based on meters, while the actual coordinates in the dataset are in decimal degrees. If you look at the properties of the raster in GIS (or using gdalinfo) what do you see for the extent and the pixel size?

Also, if this alpha = 180000:

alpha = (
            scenario_variables['alpha_value'][species] /
            landcover_mean_pixel_size)

And the alpha_value from your table is 500, then landcover_mean_pixel_size would equal 0.002777. This value is suspicious because 0.002777 decimal degrees equals roughly 300 meters (at low latitudes).

1 Like

Hey @dave, you are completely correct. My issue – the NoneType values as well as the runtime error – were the result of my LULC raster not being in a metered projection. Both the vector data for the country borders and the raster data for the ESA LULC maps were in lat/lon WS84. So when I ran the pollination invest model, it failed because the pixel size was read in as decimals (arc seconds I believe…). So I used gdal.Warp() to convert the coordinate reference system (CRS) into World Robinson (‘ESRI:54030’).

This has resolved my issues and I am able to complete the invest crop pollination model. I am currently scaling up the process for the full sample and the full set of years.

For future users, the solution I make in my clipping of the LULC is provided in the following code. Then the previous invest model py script should run.

"""
Purpose: 
1. Take the ESA LULC maps and crop to the countries of interest.
2. Reproject into metered data (world robinson)
3. Do this for full range of ESA data (1992 - 2020)
"""

# Dependencies
import os
from osgeo import gdal, ogr, osr
import pygeoprocessing as pygeo 
import geopandas as gpd

# Sample: Countries
sample_countries = ['ETH', 'NER', 'NGA', 'UGA', 'TZA', 'MWI', 'USA']
sample_years = range(1992, 2021)

# Define user and project directories
user_dir = os.path.expanduser('~')
cur_dir = "D:/pollinator-pesticide"
os.makedirs(cur_dir, exist_ok = True)

# Define paths to country vector files
vector_path = os.path.join(cur_dir, 'raw-data', 'borders', 'gadm_410-levels.gpkg')
# Define the subset of countries I care about
vector_subset_path = os.path.join(cur_dir, 'raw-data', 'borders', 'gadm_410-levels-subset.gpkg')

# Only subset if not already subset
if not os.path.exists(vector_subset_path):
    # Subset vector path to countries 
    gdf = gpd.read_file(vector_path, layer="ADM_2") # read in only layer ADM2
    # filter  on country 
    gdf_subset = gdf[gdf["GID_0"].isin(sample_countries)]
    # Save as new geopackage 
    gdf_subset.to_file(vector_subset_path)
else:
    gdf_subset = gpd.read_file(vector_subset_path)

# Clip the lulc raster data to the country vector borders
def clip_raster_to_vector(raster_path, output_raster_path):
    """
    Clips the input raster to the extent of the vector shapefile.
    """

    # Get NoData value from input raster
    nodata_val = pygeo.geoprocessing.get_raster_info(raster_path)['nodata'][0]
    
    # Perform clipping
    gdal.Warp(output_raster_path, raster_path, 
            cutlineDSName=vector_subset_path,
            cropToCutline=True,
            # cutlineLayer='ADM_0',
            # cutlineWhere=f"GID_0 = '{country}'",
            srcNodata=nodata_val, 
            dstNodata=nodata_val)
    print(f"Clipping completed: {output_raster_path}")

# Loop through LULC ESA years to clip raster to subset of countries and reproject
for year in sample_years:
    # Define raster paths
    output_raster_dir = os.path.join(cur_dir, 'raw-data', 'pollinators', 'lulc_cropped')
    output_raster_path = os.path.join(output_raster_dir, f'lulc_esa_{year}.tif')
    raster_dir = os.path.join(cur_dir, 'raw-data', 'pollinators', 'lulc_esa')
    raster_path = os.path.join(raster_dir, f'lulc_esa_{year}.tif')

    # Clip the raster if it has not been clipped yet
    if not os.path.exists(output_raster_path):
        clip_raster_to_vector(raster_path, output_raster_path)

    # Define raster paths
    output_raster_dir = os.path.join(cur_dir, 'raw-data', 'pollinators', 'lulc_cropped')
    raster_path = os.path.join(output_raster_dir, f'lulc_esa_{year}.tif')
    output_raster_path = os.path.join(output_raster_dir, f'lulc_esa_{year}_reproject.tif')

    # Reproject the clipped raster to be in meters (World Robinson)
    gdal.Warp(output_raster_path, raster_path, dstSRS='ESRI:54030')

    # Update
    print(f'Completed LULC Clip and Reproject for year:  {year}')

Thank you @dave !

1 Like

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.