diff --git a/docs/usage/tutorials/sampling_training_data.ipynb b/docs/usage/tutorials/sampling_training_data.ipynb index 10cbd5cd0..f4d6de342 100644 --- a/docs/usage/tutorials/sampling_training_data.ipynb +++ b/docs/usage/tutorials/sampling_training_data.ipynb @@ -517,7 +517,8 @@ " # resize chips to 256x256 before returning\n", " out_size=256,\n", " # allow windows to overflow the extent by 100 pixels\n", - " padding=100\n", + " padding=100,\n", + " max_windows=10\n", ")\n", "\n", "img_full = ds.scene.raster_source[:, :]\n", diff --git a/docs/usage/tutorials/temporal.ipynb b/docs/usage/tutorials/temporal.ipynb index 6834926cb..2ac4f89b7 100644 --- a/docs/usage/tutorials/temporal.ipynb +++ b/docs/usage/tutorials/temporal.ipynb @@ -433,7 +433,7 @@ "source": [ "scene = Scene(id='test_scene', raster_source=raster_source)\n", "ds = SemanticSegmentationRandomWindowGeoDataset(\n", - " scene=scene, size_lims=(256, 256 + 1), out_size=256, return_window=True)" + " scene=scene, size_lims=(256, 256 + 1), out_size=256, max_windows=10, return_window=True)" ] }, { diff --git a/rastervision_aws_batch/requirements.txt b/rastervision_aws_batch/requirements.txt index 7b2daa7ca..e038a41d9 100644 --- a/rastervision_aws_batch/requirements.txt +++ b/rastervision_aws_batch/requirements.txt @@ -1,3 +1,2 @@ rastervision_pipeline==0.31.0 boto3==1.34.155 -awscli==1.33.37 diff --git a/rastervision_aws_s3/requirements.txt b/rastervision_aws_s3/requirements.txt index c01e122b8..797126acd 100644 --- a/rastervision_aws_s3/requirements.txt +++ b/rastervision_aws_s3/requirements.txt @@ -1,5 +1,4 @@ rastervision_pipeline==0.31.0 boto3==1.34.155 -awscli==1.33.37 tqdm==4.66.5 - +botocore==1.34.158 diff --git a/rastervision_core/rastervision/core/box.py b/rastervision_core/rastervision/core/box.py index 31c21aab1..164459fa7 100644 --- a/rastervision_core/rastervision/core/box.py +++ b/rastervision_core/rastervision/core/box.py @@ -1,4 +1,4 @@ -from typing import (TYPE_CHECKING, Literal, Self) +from typing import (TYPE_CHECKING, Literal) from collections.abc import Callable from pydantic import NonNegativeInt as NonNegInt, PositiveInt as PosInt import math @@ -14,6 +14,7 @@ ensure_tuple) if TYPE_CHECKING: + from typing import Self from shapely.geometry import MultiPolygon from shapely.geometry.base import BaseGeometry @@ -44,11 +45,11 @@ def __init__(self, ymin: int, xmin: int, ymax: int, xmax: int): self.ymax = ymax self.xmax = xmax - def __eq__(self, other: Self) -> bool: + def __eq__(self, other: 'Self') -> bool: """Return true if other has same coordinates.""" return self.tuple_format() == other.tuple_format() - def __ne__(self, other: Self): + def __ne__(self, other: 'Self'): """Return true if other has different coordinates.""" return self.tuple_format() != other.tuple_format() @@ -63,7 +64,7 @@ def width(self) -> int: return self.xmax - self.xmin @property - def extent(self) -> Self: + def extent(self) -> 'Self': """Return a Box(0, 0, h, w) representing the size of this Box.""" return Box(0, 0, self.height, self.width) @@ -77,7 +78,7 @@ def area(self) -> int: """Return area of Box.""" return self.height * self.width - def normalize(self) -> Self: + def normalize(self) -> 'Self': """Ensure ymin <= ymax and xmin <= xmax.""" ymin, ymax = sorted((self.ymin, self.ymax)) xmin, xmax = sorted((self.xmin, self.xmax)) @@ -110,7 +111,7 @@ def npbox_format(self) -> np.ndarray: return np.array(self.tuple_format(), dtype=float) @staticmethod - def to_npboxes(boxes: list[Self]) -> np.ndarray: + def to_npboxes(boxes: list['Self']) -> np.ndarray: """Return nx4 numpy array from list of Box.""" nb_boxes = len(boxes) npboxes = np.empty((nb_boxes, 4)) @@ -139,7 +140,7 @@ def geojson_coordinates(self) -> list[tuple[int, int]]: sw = [self.xmax, self.ymin] return [nw, ne, se, sw, nw] - def make_random_square_container(self, size: int) -> Self: + def make_random_square_container(self, size: int) -> 'Self': """Return a new square Box that contains this Box. Args: @@ -147,7 +148,7 @@ def make_random_square_container(self, size: int) -> Self: """ return self.make_random_box_container(size, size) - def make_random_box_container(self, out_h: int, out_w: int) -> Self: + def make_random_box_container(self, out_h: int, out_w: int) -> 'Self': """Return a new rectangular Box that contains this Box. Args: @@ -173,7 +174,7 @@ def make_random_box_container(self, out_h: int, out_w: int) -> Self: return Box(out_ymin, out_xmin, out_ymin + out_h, out_xmin + out_w) - def make_random_square(self, size: int) -> Self: + def make_random_square(self, size: int) -> 'Self': """Return new randomly positioned square Box that lies inside this Box. Args: @@ -197,7 +198,7 @@ def make_random_square(self, size: int) -> Self: return Box.make_square(rand_y, rand_x, size) - def intersection(self, other: Self) -> Self: + def intersection(self, other: 'Self') -> 'Self': """Return the intersection of this Box and the other. Args: @@ -218,7 +219,7 @@ def intersection(self, other: Self) -> Self: ymax = min(box1.ymax, box2.ymax) return Box(xmin=xmin, ymin=ymin, xmax=xmax, ymax=ymax) - def intersects(self, other: Self) -> bool: + def intersects(self, other: 'Self') -> bool: box1 = self.normalize() box2 = other.normalize() if box1.ymax <= box2.ymin or box1.ymin >= box2.ymax: @@ -228,7 +229,7 @@ def intersects(self, other: Self) -> bool: return True @classmethod - def from_npbox(cls, npbox: np.ndarray) -> Self: + def from_npbox(cls, npbox: np.ndarray) -> 'Self': """Return new Box based on npbox format. Args: @@ -237,13 +238,13 @@ def from_npbox(cls, npbox: np.ndarray) -> Self: return Box(*npbox) @classmethod - def from_shapely(cls, shape: 'BaseGeometry') -> Self: + def from_shapely(cls, shape: 'BaseGeometry') -> 'Self': """Instantiate from the bounds of a shapely geometry.""" xmin, ymin, xmax, ymax = shape.bounds return Box(ymin, xmin, ymax, xmax) @classmethod - def from_rasterio(cls, rio_window: RioWindow) -> Self: + def from_rasterio(cls, rio_window: RioWindow) -> 'Self': """Instantiate from a rasterio window.""" yslice, xslice = rio_window.toslices() return Box(yslice.start, xslice.start, yslice.stop, xslice.stop) @@ -274,12 +275,12 @@ def to_slices(self, h_step: int | None = None, return slice(self.ymin, self.ymax, h_step), slice( self.xmin, self.xmax, w_step) - def translate(self, dy: int, dx: int) -> Self: + def translate(self, dy: int, dx: int) -> 'Self': """Translate window along y and x axes by the given distances.""" ymin, xmin, ymax, xmax = self return Box(ymin + dy, xmin + dx, ymax + dy, xmax + dx) - def to_global_coords(self, bbox: Self) -> Self: + def to_global_coords(self, bbox: 'Self') -> 'Self': """Go from bbox coords to global coords. E.g., Given a box Box(20, 20, 40, 40) and bbox Box(20, 20, 100, 100), @@ -289,7 +290,7 @@ def to_global_coords(self, bbox: Self) -> Self: """ return self.translate(dy=bbox.ymin, dx=bbox.xmin) - def to_local_coords(self, bbox: Self) -> Self: + def to_local_coords(self, bbox: 'Self') -> 'Self': """Go from to global coords bbox coords. E.g., Given a box Box(40, 40, 60, 60) and bbox Box(20, 20, 100, 100), @@ -299,7 +300,7 @@ def to_local_coords(self, bbox: Self) -> Self: """ return self.translate(dy=-bbox.ymin, dx=-bbox.xmin) - def reproject(self, transform_fn: Callable[[tuple], tuple]) -> Self: + def reproject(self, transform_fn: Callable[[tuple], tuple]) -> 'Self': """Reprojects this box based on a transform function. Args: @@ -313,11 +314,11 @@ def reproject(self, transform_fn: Callable[[tuple], tuple]) -> Self: return Box(ymin, xmin, ymax, xmax) @staticmethod - def make_square(ymin, xmin, size) -> Self: + def make_square(ymin, xmin, size) -> 'Self': """Return new square Box.""" return Box(ymin, xmin, ymin + size, xmin + size) - def center_crop(self, edge_offset_y: int, edge_offset_x: int) -> Self: + def center_crop(self, edge_offset_y: int, edge_offset_x: int) -> 'Self': """Return Box whose sides are eroded by the given offsets. Box(0, 0, 10, 10).center_crop(2, 4) == Box(2, 4, 8, 6) @@ -325,11 +326,11 @@ def center_crop(self, edge_offset_y: int, edge_offset_x: int) -> Self: return Box(self.ymin + edge_offset_y, self.xmin + edge_offset_x, self.ymax - edge_offset_y, self.xmax - edge_offset_x) - def erode(self, erosion_sz) -> Self: + def erode(self, erosion_sz) -> 'Self': """Return new Box whose sides are eroded by erosion_sz.""" return self.center_crop(erosion_sz, erosion_sz) - def buffer(self, buffer_sz: float, max_extent: Self) -> Self: + def buffer(self, buffer_sz: float, max_extent: 'Self') -> 'Self': """Return new Box whose sides are buffered by buffer_sz. The resulting box is clipped so that the values of the corners are @@ -351,7 +352,7 @@ def buffer(self, buffer_sz: float, max_extent: Self) -> Self: min(max_extent.width, int(self.xmax) + delta_width)) - def pad(self, ymin: int, xmin: int, ymax: int, xmax: int) -> Self: + def pad(self, ymin: int, xmin: int, ymax: int, xmax: int) -> 'Self': """Pad sides by the given amount.""" return Box( ymin=self.ymin - ymin, @@ -359,7 +360,7 @@ def pad(self, ymin: int, xmin: int, ymax: int, xmax: int) -> Self: ymax=self.ymax + ymax, xmax=self.xmax + xmax) - def copy(self) -> Self: + def copy(self) -> 'Self': return Box(*self) def get_windows( @@ -368,7 +369,7 @@ def get_windows( stride: PosInt | tuple[PosInt, PosInt], padding: NonNegInt | tuple[NonNegInt, NonNegInt] | None = None, pad_direction: Literal['both', 'start', 'end'] = 'end' - ) -> list[Self]: + ) -> list['Self']: """Return sliding windows for given size, stride, and padding. Each of size, stride, and padding can be either a positive int or @@ -452,13 +453,13 @@ def to_dict(self) -> dict[str, int]: } @classmethod - def from_dict(cls, d: dict) -> Self: + def from_dict(cls, d: dict) -> 'Self': return cls(d['ymin'], d['xmin'], d['ymax'], d['xmax']) @staticmethod - def filter_by_aoi(windows: list[Self], + def filter_by_aoi(windows: list['Self'], aoi_polygons: list[Polygon], - within: bool = True) -> list[Self]: + within: bool = True) -> list['Self']: """Filters windows by a list of AOI polygons Args: @@ -478,7 +479,7 @@ def filter_by_aoi(windows: list[Self], return out @staticmethod - def within_aoi(window: Self, + def within_aoi(window: 'Self', aoi_polygons: Polygon | list[Polygon]) -> bool: """Check if window is within the union of given AOI polygons.""" aoi_polygons: Polygon | MultiPolygon = unary_union(aoi_polygons) @@ -487,7 +488,7 @@ def within_aoi(window: Self, return out @staticmethod - def intersects_aoi(window: Self, + def intersects_aoi(window: 'Self', aoi_polygons: Polygon | list[Polygon]) -> bool: """Check if window intersects with the union of given AOI polygons.""" aoi_polygons: Polygon | MultiPolygon = unary_union(aoi_polygons) @@ -495,7 +496,7 @@ def intersects_aoi(window: Self, out = aoi_polygons.intersects(w) return out - def __contains__(self, query: Self | tuple[int, int]) -> bool: + def __contains__(self, query: 'Self | tuple[int, int]') -> bool: """Check if box or point is contained within this box. Args: diff --git a/rastervision_core/rastervision/core/data/class_config.py b/rastervision_core/rastervision/core/data/class_config.py index 89b542ddb..3359ec0a8 100644 --- a/rastervision_core/rastervision/core/data/class_config.py +++ b/rastervision_core/rastervision/core/data/class_config.py @@ -1,9 +1,12 @@ -from typing import Self +from typing import TYPE_CHECKING from rastervision.pipeline.config import (Config, register_config, ConfigError, Field, model_validator) from rastervision.core.data.utils import color_to_triple, normalize_color +if TYPE_CHECKING: + from typing import Self + DEFAULT_NULL_CLASS_NAME = 'null' DEFAULT_NULL_CLASS_COLOR = 'black' @@ -33,7 +36,7 @@ class ClassConfig(Config): 'added automatically.') @model_validator(mode='after') - def validate_colors(self) -> Self: + def validate_colors(self) -> 'Self': """Compare length w/ names. Also auto-generate if not specified.""" names = self.names colors = self.colors @@ -47,7 +50,7 @@ def validate_colors(self) -> Self: return self @model_validator(mode='after') - def validate_null_class(self) -> Self: + def validate_null_class(self) -> 'Self': """Check if in names. If 'null' in names, use it as null class.""" names = self.names null_class = self.null_class diff --git a/rastervision_core/rastervision/core/data/crs_transformer/rasterio_crs_transformer.py b/rastervision_core/rastervision/core/data/crs_transformer/rasterio_crs_transformer.py index fa352fd37..14263b8dc 100644 --- a/rastervision_core/rastervision/core/data/crs_transformer/rasterio_crs_transformer.py +++ b/rastervision_core/rastervision/core/data/crs_transformer/rasterio_crs_transformer.py @@ -1,4 +1,4 @@ -from typing import Any, Self +from typing import TYPE_CHECKING, Any from pyproj import Transformer import numpy as np @@ -9,6 +9,9 @@ from rastervision.core.data.crs_transformer import (CRSTransformer, IdentityCRSTransformer) +if TYPE_CHECKING: + from typing import Self + class RasterioCRSTransformer(CRSTransformer): """Transformer for a RasterioRasterSource.""" diff --git a/rastervision_core/rastervision/core/data/label/chip_classification_labels.py b/rastervision_core/rastervision/core/data/label/chip_classification_labels.py index 1d8ddc8d0..3a0b93ada 100644 --- a/rastervision_core/rastervision/core/data/label/chip_classification_labels.py +++ b/rastervision_core/rastervision/core/data/label/chip_classification_labels.py @@ -1,4 +1,4 @@ -from typing import (TYPE_CHECKING, Any, Iterable, Self) +from typing import (TYPE_CHECKING, Any, Iterable) from dataclasses import dataclass import numpy as np @@ -8,6 +8,7 @@ from rastervision.core.utils.types import Vector if TYPE_CHECKING: + from typing import Self from rastervision.core.data import (ClassConfig, CRSTransformer) from shapely.geometry import Polygon @@ -38,11 +39,11 @@ def __init__(self, def __len__(self) -> int: return len(self.cell_to_label) - def __eq__(self, other: Self) -> bool: + def __eq__(self, other: 'Self') -> bool: return (isinstance(other, ChipClassificationLabels) and self.cell_to_label == other.cell_to_label) - def __add__(self, other: Self) -> Self: + def __add__(self, other: 'Self') -> 'Self': result = ChipClassificationLabels() result.extend(self) result.extend(other) @@ -66,7 +67,7 @@ def from_predictions(cls, windows: Iterable['Box'], return super().from_predictions(windows, predictions) @classmethod - def make_empty(cls) -> Self: + def make_empty(cls) -> 'Self': return ChipClassificationLabels() def filter_by_aoi(self, aoi_polygons: Iterable['Polygon']): @@ -140,7 +141,7 @@ def get_values(self) -> list[ClassificationLabel]: """Return list of class_ids and scores for all cells.""" return list(self.cell_to_label.values()) - def extend(self, labels: Self) -> None: + def extend(self, labels: 'Self') -> None: """Adds cells contained in labels. Args: diff --git a/rastervision_core/rastervision/core/data/label/labels.py b/rastervision_core/rastervision/core/data/label/labels.py index 5fd5b7c99..4f43cfabc 100644 --- a/rastervision_core/rastervision/core/data/label/labels.py +++ b/rastervision_core/rastervision/core/data/label/labels.py @@ -1,9 +1,10 @@ """Defines the abstract Labels class.""" -from typing import TYPE_CHECKING, Any, Iterable, Self +from typing import TYPE_CHECKING, Any, Iterable from abc import ABC, abstractmethod if TYPE_CHECKING: + from typing import Self from shapely.geometry import Polygon from rastervision.core.box import Box @@ -16,14 +17,14 @@ class Labels(ABC): """ @abstractmethod - def __add__(self, other: Self): + def __add__(self, other: 'Self'): """Add labels to these labels. Returns a concatenation of this and the other labels. """ @abstractmethod - def filter_by_aoi(self, aoi_polygons: list['Polygon']) -> Self: + def filter_by_aoi(self, aoi_polygons: list['Polygon']) -> 'Self': """Return a copy of these labels filtered by given AOI polygons. Args: @@ -41,7 +42,7 @@ def __setitem__(self, key, value): @classmethod @abstractmethod - def make_empty(cls) -> Self: + def make_empty(cls) -> 'Self': """Instantiate an empty instance of this class. Returns: @@ -51,7 +52,7 @@ def make_empty(cls) -> Self: @classmethod def from_predictions(cls, windows: Iterable['Box'], - predictions: Iterable[Any]) -> Self: + predictions: Iterable[Any]) -> 'Self': """Instantiate from windows and their corresponding predictions. This makes no assumptions about the type or format of the predictions. diff --git a/rastervision_core/rastervision/core/data/label/object_detection_labels.py b/rastervision_core/rastervision/core/data/label/object_detection_labels.py index f6727ad14..ecca905a3 100644 --- a/rastervision_core/rastervision/core/data/label/object_detection_labels.py +++ b/rastervision_core/rastervision/core/data/label/object_detection_labels.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Iterable, Self +from typing import TYPE_CHECKING, Iterable import numpy as np from shapely.geometry import shape @@ -10,6 +10,7 @@ non_max_suppression) if TYPE_CHECKING: + from typing import Self from rastervision.core.data import (ClassConfig, CRSTransformer) from shapely.geometry import Polygon @@ -43,10 +44,10 @@ def __init__(self, scores = np.ones(class_ids.shape) self.boxlist.add_field('scores', scores) - def __add__(self, other: Self) -> Self: + def __add__(self, other: 'Self') -> 'Self': return ObjectDetectionLabels.concatenate(self, other) - def __eq__(self, other: Self) -> bool: + def __eq__(self, other: 'Self') -> bool: return (isinstance(other, ObjectDetectionLabels) and self.to_dict() == other.to_dict()) @@ -60,10 +61,10 @@ def __setitem__(self, window: Box, item: dict[str, np.ndarray]): concatenated_labels = self + new_labels self.boxlist = concatenated_labels.boxlist - def __getitem__(self, window: Box) -> Self: + def __getitem__(self, window: Box) -> 'Self': return ObjectDetectionLabels.get_overlapping(self, window) - def assert_equal(self, expected_labels: Self): + def assert_equal(self, expected_labels: 'Self'): np.testing.assert_array_equal(self.get_npboxes(), expected_labels.get_npboxes()) np.testing.assert_array_equal(self.get_class_ids(), @@ -95,7 +96,7 @@ def filter_by_aoi(self, aoi_polygons: Iterable['Polygon']): np.array(new_boxes), np.array(new_class_ids), np.array(new_scores)) @classmethod - def make_empty(cls) -> Self: + def make_empty(cls) -> 'Self': npboxes = np.empty((0, 4)) class_ids = np.empty((0, )) scores = np.empty((0, )) @@ -113,7 +114,7 @@ def from_boxlist(boxlist: NpBoxList): def from_geojson(geojson: dict, bbox: Box | None = None, ioa_thresh: float = 0.8, - clip: bool = True) -> Self: + clip: bool = True) -> 'Self': """Convert GeoJSON to ObjectDetectionLabels object. If bbox is provided, filter out the boxes that lie "more than a little @@ -228,10 +229,10 @@ def normalized_to_local(npboxes: np.ndarray, window: Box): return npboxes * np.array([[height, width, height, width]]) @staticmethod - def get_overlapping(labels: Self, + def get_overlapping(labels: 'Self', window: Box, ioa_thresh: float = 0.5, - clip: bool = False) -> Self: + clip: bool = False) -> 'Self': """Return subset of labels that overlap with window. Args: @@ -253,16 +254,16 @@ def get_overlapping(labels: Self, return ObjectDetectionLabels.from_boxlist(boxlist) @staticmethod - def concatenate(labels1: Self, labels2: Self) -> Self: + def concatenate(labels1: 'Self', labels2: 'Self') -> 'Self': """Return concatenation of labels.""" new_boxlist = concatenate([labels1.to_boxlist(), labels2.to_boxlist()]) return ObjectDetectionLabels.from_boxlist(new_boxlist) @staticmethod - def prune_duplicates(labels: Self, + def prune_duplicates(labels: 'Self', score_thresh: float, merge_thresh: float, - max_output_size: int | None = None) -> Self: + max_output_size: int | None = None) -> 'Self': """Remove duplicate boxes via non-maximum suppression. Args: diff --git a/rastervision_core/rastervision/core/data/label/semantic_segmentation_labels.py b/rastervision_core/rastervision/core/data/label/semantic_segmentation_labels.py index 1a07499c5..d611469e8 100644 --- a/rastervision_core/rastervision/core/data/label/semantic_segmentation_labels.py +++ b/rastervision_core/rastervision/core/data/label/semantic_segmentation_labels.py @@ -1,4 +1,4 @@ -from typing import (TYPE_CHECKING, Any, Iterable, Self, Sequence) +from typing import (TYPE_CHECKING, Any, Iterable, Sequence) from abc import abstractmethod import numpy as np @@ -10,6 +10,7 @@ from rastervision.core.data.label.utils import discard_prediction_edges if TYPE_CHECKING: + from typing import Self from shapely.geometry import Polygon from rastervision.core.data import (ClassConfig, CRSTransformer, VectorOutputConfig) @@ -32,7 +33,7 @@ def __init__(self, extent: Box, num_classes: int, dtype: np.dtype): self.dtype = dtype @abstractmethod - def __add__(self, other: Self) -> Self: + def __add__(self, other: 'Self') -> 'Self': """Merge self with other labels.""" def __setitem__(self, window: Box, values: np.ndarray) -> None: @@ -94,7 +95,7 @@ def get_windows(self, **kwargs) -> list[Box]: return self.extent.get_windows(size, size, **kwargs) def filter_by_aoi(self, aoi_polygons: list['Polygon'], null_class_id: int, - **kwargs) -> Self: + **kwargs) -> 'Self': """Keep only the values that lie inside the AOI. This is an inplace operation. @@ -155,7 +156,7 @@ def transform_shape(x, y, z=None): @classmethod def make_empty(cls, extent: Box, num_classes: int, - smooth: bool = False) -> Self: + smooth: bool = False) -> 'Self': """Instantiate an empty instance. Args: @@ -188,7 +189,7 @@ def from_predictions(cls, extent: Box, num_classes: int, smooth: bool = False, - crop_sz: int | None = None) -> Self: + crop_sz: int | None = None) -> 'Self': """Instantiate from windows and their corresponding predictions. Args: @@ -270,7 +271,7 @@ def __init__(self, extent: Box, num_classes: int, dtype: Any = np.uint8): # track which pixels have been hit at all self.hit_mask = np.zeros((self.height, self.width), dtype=bool) - def __add__(self, other: Self) -> Self: + def __add__(self, other: 'Self') -> 'Self': """Merge self with other labels by adding the pixel counts.""" if self.extent != other.extent: raise ValueError('Cannot add labels with unqeual extents.') @@ -278,7 +279,7 @@ def __add__(self, other: Self) -> Self: self.pixel_counts += other.pixel_counts return self - def __eq__(self, other: Self) -> bool: + def __eq__(self, other: 'Self') -> bool: if not isinstance(other, SemanticSegmentationDiscreteLabels): return False if self.extent != other.extent: @@ -347,7 +348,7 @@ def mask_fill(self, window: Box, mask: np.ndarray, self.pixel_counts[class_id, y0:y1, x0:x1][mask] = 1 @classmethod - def make_empty(cls, extent: Box, num_classes: int) -> Self: + def make_empty(cls, extent: Box, num_classes: int) -> 'Self': """Instantiate an empty instance.""" return cls(extent=extent, num_classes=num_classes) @@ -357,7 +358,7 @@ def from_predictions(cls, predictions: Iterable[Any], extent: Box, num_classes: int, - crop_sz: int | None = None) -> Self: + crop_sz: int | None = None) -> 'Self': labels = cls.make_empty(extent, num_classes) labels.add_predictions(windows, predictions, crop_sz=crop_sz) return labels @@ -447,7 +448,7 @@ def __init__(self, (self.num_classes, self.height, self.width), dtype=self.dtype) self.pixel_hits = np.zeros((self.height, self.width), dtype=dtype_hits) - def __add__(self, other: Self) -> Self: + def __add__(self, other: 'Self') -> 'Self': """Merge self with other by adding pixel scores and hits.""" if self.extent != other.extent: raise ValueError('Cannot add labels with unqeual extents.') @@ -456,7 +457,7 @@ def __add__(self, other: Self) -> Self: self.pixel_hits += other.pixel_hits return self - def __eq__(self, other: Self) -> bool: + def __eq__(self, other: 'Self') -> bool: if not isinstance(other, SemanticSegmentationSmoothLabels): return False if self.extent != other.extent: @@ -523,7 +524,7 @@ def mask_fill(self, window: Box, mask: np.ndarray, self.pixel_hits[y0:y1, x0:x1][mask] = 1 @classmethod - def make_empty(cls, extent: Box, num_classes: int) -> Self: + def make_empty(cls, extent: Box, num_classes: int) -> 'Self': """Instantiate an empty instance.""" return cls(extent=extent, num_classes=num_classes) @@ -533,7 +534,7 @@ def from_predictions(cls, predictions: Iterable[Any], extent: Box, num_classes: int, - crop_sz: int | None = None) -> Self: + crop_sz: int | None = None) -> 'Self': labels = cls.make_empty(extent, num_classes) labels.add_predictions(windows, predictions, crop_sz=crop_sz) return labels diff --git a/rastervision_core/rastervision/core/data/label_source/chip_classification_label_source_config.py b/rastervision_core/rastervision/core/data/label_source/chip_classification_label_source_config.py index 70b73c5eb..e030fa277 100644 --- a/rastervision_core/rastervision/core/data/label_source/chip_classification_label_source_config.py +++ b/rastervision_core/rastervision/core/data/label_source/chip_classification_label_source_config.py @@ -1,4 +1,4 @@ -from typing import Self +from typing import TYPE_CHECKING from rastervision.core.data.vector_source import (VectorSourceConfig) from rastervision.core.data.label_source import (LabelSourceConfig, @@ -8,6 +8,9 @@ from rastervision.core.data.vector_transformer import ( ClassInferenceTransformerConfig, BufferTransformerConfig) +if TYPE_CHECKING: + from typing import Self + def cc_label_source_config_upgrader(cfg_dict: dict, version: int) -> dict: if version == 4: @@ -86,7 +89,7 @@ def ensure_required_transformers( return v @model_validator(mode='after') - def ensure_bg_class_id_if_inferring(self) -> Self: + def ensure_bg_class_id_if_inferring(self) -> 'Self': infer_cells = self.infer_cells has_bg_class_id = self.background_class_id is not None if infer_cells and not has_bg_class_id: diff --git a/rastervision_core/rastervision/core/data/raster_source/multi_raster_source.py b/rastervision_core/rastervision/core/data/raster_source/multi_raster_source.py index 806ecde7a..124b1d64d 100644 --- a/rastervision_core/rastervision/core/data/raster_source/multi_raster_source.py +++ b/rastervision_core/rastervision/core/data/raster_source/multi_raster_source.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Sequence, Self +from typing import TYPE_CHECKING, Sequence from pydantic import NonNegativeInt as NonNegInt import numpy as np @@ -10,6 +10,7 @@ from rastervision.core.data.utils import all_equal if TYPE_CHECKING: + from typing import Self from rastervision.core.data import RasterTransformer, CRSTransformer @@ -81,7 +82,7 @@ def from_stac( channel_order: Sequence[int] | None = None, bbox: Box | tuple[int, int, int, int] | None = None, bbox_map_coords: Box | tuple[int, int, int, int] | None = None, - allow_streaming: bool = False) -> Self: + allow_streaming: bool = False) -> 'Self': """Construct a ``MultiRasterSource`` from a STAC Item. This creates a :class:`.RasterioSource` for each asset and puts all diff --git a/rastervision_core/rastervision/core/data/raster_source/multi_raster_source_config.py b/rastervision_core/rastervision/core/data/raster_source/multi_raster_source_config.py index 95ac5beb2..534f006a9 100644 --- a/rastervision_core/rastervision/core/data/raster_source/multi_raster_source_config.py +++ b/rastervision_core/rastervision/core/data/raster_source/multi_raster_source_config.py @@ -1,4 +1,4 @@ -from typing import Self +from typing import TYPE_CHECKING from typing_extensions import Annotated from pydantic import NonNegativeInt as NonNegInt @@ -9,6 +9,9 @@ from rastervision.core.data.raster_source import (RasterSourceConfig, MultiRasterSource) +if TYPE_CHECKING: + from typing import Self + def multi_rs_config_upgrader(cfg_dict: dict, version: int) -> dict: if version == 1: @@ -42,7 +45,7 @@ class MultiRasterSourceConfig(RasterSourceConfig): 'of shape (T, H, W, C) instead of concatenating bands.') @model_validator(mode='after') - def validate_primary_source_idx(self) -> Self: + def validate_primary_source_idx(self) -> 'Self': primary_source_idx = self.primary_source_idx raster_sources = self.raster_sources if not (0 <= primary_source_idx < len(raster_sources)): @@ -51,7 +54,7 @@ def validate_primary_source_idx(self) -> Self: return self @model_validator(mode='after') - def validate_temporal(self) -> Self: + def validate_temporal(self) -> 'Self': if self.temporal and self.channel_order is not None: raise ValueError( 'Setting channel_order is not allowed if temporal=True.') diff --git a/rastervision_core/rastervision/core/data/raster_transformer/stats_transformer.py b/rastervision_core/rastervision/core/data/raster_transformer/stats_transformer.py index 87655f11c..5d8447351 100644 --- a/rastervision_core/rastervision/core/data/raster_transformer/stats_transformer.py +++ b/rastervision_core/rastervision/core/data/raster_transformer/stats_transformer.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Self, Sequence +from typing import TYPE_CHECKING, Sequence import numpy as np @@ -7,6 +7,7 @@ from rastervision.pipeline.utils import repr_with_args if TYPE_CHECKING: + from typing import Self from rastervision.core.data import RasterSource @@ -86,7 +87,7 @@ def from_raster_sources(cls, raster_sources: list['RasterSource'], sample_prob: float | None = 0.1, max_stds: float = 3., - chip_sz: int = 300) -> Self: + chip_sz: int = 300) -> 'Self': """Build with stats from the given raster sources. Args: @@ -114,7 +115,7 @@ def from_raster_sources(cls, def from_stats_json(cls, uri: str, channel_order: list[int] | None = None, - **kwargs) -> Self: + **kwargs) -> 'Self': """Build with stats from a JSON file. The file is expected to be in the same format as written by @@ -138,7 +139,7 @@ def from_stats_json(cls, def from_raster_stats(cls, stats: RasterStats, channel_order: list[int] | None = None, - **kwargs) -> Self: + **kwargs) -> 'Self': """Build with stats from a :class:`.RasterStats` instance. The file is expected to be in the same format as written by diff --git a/rastervision_core/rastervision/core/evaluation/class_evaluation_item.py b/rastervision_core/rastervision/core/evaluation/class_evaluation_item.py index 7f981070c..241c0ac23 100644 --- a/rastervision_core/rastervision/core/evaluation/class_evaluation_item.py +++ b/rastervision_core/rastervision/core/evaluation/class_evaluation_item.py @@ -1,9 +1,12 @@ """Defines ``ClassEvaluationItem``.""" -from typing import Self +from typing import TYPE_CHECKING import numpy as np from rastervision.core.evaluation import EvaluationItem +if TYPE_CHECKING: + from typing import Self + class ClassEvaluationItem(EvaluationItem): """A wrapper around a binary (2x2) confusion matrix of the form @@ -55,7 +58,7 @@ def __init__(self, @classmethod def from_multiclass_conf_mat(cls, conf_mat: np.ndarray, class_id: int, - class_name: str, **kwargs) -> Self: + class_name: str, **kwargs) -> 'Self': """Construct from a multi-class confusion matrix and a target class ID. Args: diff --git a/rastervision_core/rastervision/core/raster_stats.py b/rastervision_core/rastervision/core/raster_stats.py index 439b4f778..01e850f70 100644 --- a/rastervision_core/rastervision/core/raster_stats.py +++ b/rastervision_core/rastervision/core/raster_stats.py @@ -1,4 +1,4 @@ -from typing import (TYPE_CHECKING, Iterable, Iterator, Self, Sequence) +from typing import (TYPE_CHECKING, Iterable, Iterator, Sequence) import numpy as np from tqdm.auto import tqdm @@ -8,6 +8,7 @@ from rastervision.core.data.utils import ensure_json_serializable if TYPE_CHECKING: + from typing import Self from rastervision.core.box import Box from rastervision.core.data import RasterSource @@ -32,7 +33,7 @@ def __init__(self, self.counts = np.array(counts) if counts is not None else None @classmethod - def load(cls, stats_uri: str) -> Self: + def load(cls, stats_uri: str) -> 'Self': """Load stats from file.""" stats_json = file_to_json(stats_uri) assert 'means' in stats_json and 'stds' in stats_json diff --git a/rastervision_core/rastervision/core/rv_pipeline/chip_options.py b/rastervision_core/rastervision/core/rv_pipeline/chip_options.py index 0bdca95f9..da715fc2b 100644 --- a/rastervision_core/rastervision/core/rv_pipeline/chip_options.py +++ b/rastervision_core/rastervision/core/rv_pipeline/chip_options.py @@ -1,4 +1,4 @@ -from typing import (Any, Literal, Self) +from typing import (TYPE_CHECKING, Any, Literal) from enum import Enum from pydantic import NonNegativeInt as NonNegInt, PositiveInt as PosInt @@ -9,6 +9,9 @@ from rastervision.pipeline.config import (Config, ConfigError, Field, register_config, model_validator) +if TYPE_CHECKING: + from typing import Self + class WindowSamplingMethod(Enum): """Enum for window sampling methods. @@ -87,7 +90,7 @@ class WindowSamplingConfig(Config): 'intersecting the AOI will also be allowed.') @model_validator(mode='after') - def validate_options(self) -> Self: + def validate_options(self) -> 'Self': method = self.method size = self.size if method == WindowSamplingMethod.sliding: diff --git a/rastervision_core/rastervision/core/rv_pipeline/object_detection_config.py b/rastervision_core/rastervision/core/rv_pipeline/object_detection_config.py index 59a00dc53..785f8df93 100644 --- a/rastervision_core/rastervision/core/rv_pipeline/object_detection_config.py +++ b/rastervision_core/rastervision/core/rv_pipeline/object_detection_config.py @@ -1,4 +1,4 @@ -from typing import Self +from typing import TYPE_CHECKING from rastervision.pipeline.config import (Field, register_config, model_validator) @@ -7,6 +7,9 @@ from rastervision.core.data.label_store import ObjectDetectionGeoJSONStoreConfig from rastervision.core.evaluation import ObjectDetectionEvaluatorConfig +if TYPE_CHECKING: + from typing import Self + @register_config('object_detection_window_sampling') class ObjectDetectionWindowSamplingConfig(WindowSamplingConfig): @@ -61,7 +64,7 @@ class ObjectDetectionPredictOptions(PredictOptions): )) @model_validator(mode='after') - def validate_stride(self) -> Self: + def validate_stride(self) -> 'Self': if self.stride is None: self.stride = self.chip_sz // 2 return self diff --git a/rastervision_core/rastervision/core/rv_pipeline/rv_pipeline_config.py b/rastervision_core/rastervision/core/rv_pipeline/rv_pipeline_config.py index 3b1d32e2e..fb68d911d 100644 --- a/rastervision_core/rastervision/core/rv_pipeline/rv_pipeline_config.py +++ b/rastervision_core/rastervision/core/rv_pipeline/rv_pipeline_config.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Self +from typing import TYPE_CHECKING from os.path import join from rastervision.pipeline.pipeline_config import PipelineConfig @@ -13,6 +13,7 @@ model_validator) if TYPE_CHECKING: + from typing import Self from rastervision.core.backend.backend import Backend # noqa @@ -28,7 +29,7 @@ class PredictOptions(Config): 8, description='Batch size to use during prediction.') @model_validator(mode='after') - def validate_stride(self) -> Self: + def validate_stride(self) -> 'Self': if self.stride is None: self.stride = self.chip_sz return self diff --git a/rastervision_core/rastervision/core/rv_pipeline/semantic_segmentation_config.py b/rastervision_core/rastervision/core/rv_pipeline/semantic_segmentation_config.py index a84ab1900..9445cc44c 100644 --- a/rastervision_core/rastervision/core/rv_pipeline/semantic_segmentation_config.py +++ b/rastervision_core/rastervision/core/rv_pipeline/semantic_segmentation_config.py @@ -1,4 +1,4 @@ -from typing import Literal, Self +from typing import TYPE_CHECKING, Literal import logging from pydantic import NonNegativeInt as NonNegInt @@ -13,6 +13,9 @@ from rastervision.core.data import SemanticSegmentationLabelStoreConfig from rastervision.core.evaluation import SemanticSegmentationEvaluatorConfig +if TYPE_CHECKING: + from typing import Self + log = logging.getLogger(__name__) @@ -94,7 +97,7 @@ class SemanticSegmentationPredictOptions(PredictOptions): 'if stride is less than chip_sz. Defaults to None.') @model_validator(mode='after') - def set_auto_crop_sz(self) -> Self: + def set_auto_crop_sz(self) -> 'Self': if self.crop_sz == 'auto': if self.stride is None: self.validate_stride() diff --git a/rastervision_pipeline/rastervision/pipeline/config.py b/rastervision_pipeline/rastervision/pipeline/config.py index c3628386e..402ad3112 100644 --- a/rastervision_pipeline/rastervision/pipeline/config.py +++ b/rastervision_pipeline/rastervision/pipeline/config.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Literal, Self +from typing import TYPE_CHECKING, Literal from collections.abc import Callable import inspect import logging @@ -13,6 +13,7 @@ str_to_file) if TYPE_CHECKING: + from typing import Self from rastervision.pipeline.pipeline_config import PipelineConfig log = logging.getLogger(__name__) @@ -103,7 +104,7 @@ def validate_list(self, field: str, valid_options: list[str]): if val not in valid_options: raise ConfigError(f'{val} is not a valid option for {field}') - def copy(self) -> Self: + def copy(self) -> 'Self': return self.model_copy() def dict(self, with_rv_metadata: bool = False, **kwargs) -> dict: @@ -136,7 +137,7 @@ def to_file(self, uri: str, with_rv_metadata: bool = True) -> None: str_to_file(cfg_json, uri) @classmethod - def deserialize(cls, inp: 'str | dict | Config') -> Self: + def deserialize(cls, inp: 'str | dict | Config') -> 'Self': """Deserialize Config from a JSON file or dict, upgrading if possible. If ``inp`` is already a :class:`.Config`, it is returned as is. @@ -153,7 +154,7 @@ def deserialize(cls, inp: 'str | dict | Config') -> Self: raise TypeError(f'Cannot deserialize Config from type: {type(inp)}.') @classmethod - def from_file(cls, uri: str) -> Self: + def from_file(cls, uri: str) -> 'Self': """Deserialize Config from a JSON file, upgrading if possible. Args: @@ -164,7 +165,7 @@ def from_file(cls, uri: str) -> Self: return cfg @classmethod - def from_dict(cls, cfg_dict: dict) -> Self: + def from_dict(cls, cfg_dict: dict) -> 'Self': """Deserialize Config from a dict. Args: diff --git a/rastervision_pipeline/rastervision/pipeline/pipeline_config.py b/rastervision_pipeline/rastervision/pipeline/pipeline_config.py index b3c4c9d6f..6c5b96ffd 100644 --- a/rastervision_pipeline/rastervision/pipeline/pipeline_config.py +++ b/rastervision_pipeline/rastervision/pipeline/pipeline_config.py @@ -1,10 +1,11 @@ +from typing import TYPE_CHECKING from os.path import join -from typing import TYPE_CHECKING, Self from rastervision.pipeline.config import Config, Field from rastervision.pipeline.config import register_config if TYPE_CHECKING: + from typing import Self from rastervision.pipeline.pipeline import Pipeline @@ -51,7 +52,7 @@ def dict(self, with_rv_metadata: bool = True, **kwargs) -> dict: return cfg_dict @classmethod - def from_dict(cls, cfg_dict: dict) -> Self: + def from_dict(cls, cfg_dict: dict) -> 'Self': # override to retain plugin_versions from rastervision.pipeline.config import build_config, upgrade_config cfg_dict: dict = upgrade_config(cfg_dict) diff --git a/rastervision_pytorch_learner/rastervision/pytorch_learner/dataset/dataset.py b/rastervision_pytorch_learner/rastervision/pytorch_learner/dataset/dataset.py index a8b225609..1f5b4c4e0 100644 --- a/rastervision_pytorch_learner/rastervision/pytorch_learner/dataset/dataset.py +++ b/rastervision_pytorch_learner/rastervision/pytorch_learner/dataset/dataset.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Any, Literal, Self +from typing import TYPE_CHECKING, Any, Literal import logging import numpy as np @@ -16,6 +16,7 @@ TF_TYPE_TO_TF_FUNC) if TYPE_CHECKING: + from typing import Self from shapely.geometry import MultiPolygon, Polygon log = logging.getLogger(__name__) @@ -173,7 +174,7 @@ def __len__(self): raise NotImplementedError() @classmethod - def from_uris(cls, *args, **kwargs) -> Self: + def from_uris(cls, *args, **kwargs) -> 'Self': raise NotImplementedError() @@ -282,12 +283,13 @@ class RandomWindowGeoDataset(GeoDataset): def __init__( self, scene: Scene, + *, out_size: PosInt | tuple[PosInt, PosInt] | None, size_lims: tuple[PosInt, PosInt] | None = None, h_lims: tuple[PosInt, PosInt] | None = None, w_lims: tuple[PosInt, PosInt] | None = None, padding: NonNegInt | tuple[NonNegInt, NonNegInt] | None = None, - max_windows: NonNegInt | None = None, + max_windows: NonNegInt, max_sample_attempts: PosInt = 100, efficient_aoi_sampling: bool = True, within_aoi: bool = True, @@ -315,8 +317,7 @@ def __init__( sides of the raster source. If ``None``, ``padding = size``. Defaults to ``None``. max_windows: Max allowed reads. Will raise ``StopIteration`` on - further read attempts. If None, will be set to ``np.inf``. - Defaults to ``None``. + further read attempts. transform: Albumentations transform to apply to the windows. Defaults to ``None``. Each transform in Albumentations takes images of type uint8, and @@ -383,9 +384,6 @@ def __init__( padding = (max_h // 2, max_w // 2) padding: tuple[NonNegInt, NonNegInt] = ensure_tuple(padding) - if max_windows is None: - max_windows = np.iinfo('int').max - self.size_lims = size_lims self.h_lims = h_lims self.w_lims = w_lims diff --git a/rastervision_pytorch_learner/rastervision/pytorch_learner/learner.py b/rastervision_pytorch_learner/rastervision/pytorch_learner/learner.py index ce370a92c..3708d6d89 100644 --- a/rastervision_pytorch_learner/rastervision/pytorch_learner/learner.py +++ b/rastervision_pytorch_learner/rastervision/pytorch_learner/learner.py @@ -1,4 +1,4 @@ -from typing import (TYPE_CHECKING, Any, Iterator, Literal, Self) +from typing import (TYPE_CHECKING, Any, Iterator, Literal) from abc import ABC, abstractmethod from collections.abc import Callable from os.path import join, isfile, basename, isdir @@ -40,6 +40,7 @@ from rastervision.pytorch_learner.dataset.visualizer import Visualizer if TYPE_CHECKING: + from typing import Self from torch.optim import Optimizer from torch.optim.lr_scheduler import _LRScheduler from torch.utils.data import Dataset, Sampler @@ -265,7 +266,7 @@ def from_model_bundle(cls: type, cfg: 'LearnerConfig | None' = None, training: bool = False, use_onnx_model: bool | None = None, - **kwargs) -> Self: + **kwargs) -> 'Self': """Create a Learner from a model bundle. .. note:: diff --git a/rastervision_pytorch_learner/rastervision/pytorch_learner/learner_config.py b/rastervision_pytorch_learner/rastervision/pytorch_learner/learner_config.py index 1ed445586..74a9ad09d 100644 --- a/rastervision_pytorch_learner/rastervision/pytorch_learner/learner_config.py +++ b/rastervision_pytorch_learner/rastervision/pytorch_learner/learner_config.py @@ -1,4 +1,4 @@ -from typing import (TYPE_CHECKING, Any, Iterable, Literal, Self, Sequence) +from typing import (TYPE_CHECKING, Any, Iterable, Literal, Sequence) from collections.abc import Callable import os from os.path import join, isdir @@ -33,6 +33,7 @@ torch_hub_load_local, torch_hub_load_github, torch_hub_load_uri) if TYPE_CHECKING: + from typing import Self from rastervision.core.data import SceneConfig from rastervision.pytorch_learner.learner import Learner @@ -159,7 +160,7 @@ class ExternalModuleConfig(Config): False, description='Force reload of module definition.') @model_validator(mode='after') - def check_either_uri_or_repo(self) -> Self: + def check_either_uri_or_repo(self) -> 'Self': has_uri = self.uri is not None has_repo = self.github_repo is not None if has_uri == has_repo: @@ -368,7 +369,7 @@ class SolverConfig(Config): 'from this external source, using Torch Hub.') @model_validator(mode='after') - def check_no_loss_opts_if_external(self) -> Self: + def check_no_loss_opts_if_external(self) -> 'Self': has_external_loss_def = self.external_loss_def is not None has_ignore_class_index = self.ignore_class_index is not None has_class_loss_weights = self.class_loss_weights is not None @@ -687,7 +688,7 @@ def validate_augmentors(cls, v: list[str]) -> list[str]: return v @model_validator(mode='after') - def validate_plot_options(self) -> Self: + def validate_plot_options(self) -> 'Self': if self.plot_options is not None and self.img_channels is not None: self.plot_options.update(img_channels=self.img_channels) return self @@ -834,7 +835,7 @@ class ImageDataConfig(DataConfig): '(one for each group).') @model_validator(mode='after') - def validate_group_uris(self) -> Self: + def validate_group_uris(self) -> 'Self': group_train_sz = self.group_train_sz group_train_sz_rel = self.group_train_sz_rel group_uris = self.group_uris @@ -1183,7 +1184,7 @@ def __repr_args__(self): return out @model_validator(mode='after') - def validate_sampling(self) -> Self: + def validate_sampling(self) -> 'Self': if not isinstance(self.sampling, dict): return self @@ -1203,7 +1204,7 @@ def validate_sampling(self) -> Self: return self @model_validator(mode='after') - def get_class_config_from_dataset_if_needed(self) -> Self: + def get_class_config_from_dataset_if_needed(self) -> 'Self': if self.class_config is None and self.scene_dataset is not None: self.class_config = self.scene_dataset.class_config return self @@ -1377,14 +1378,14 @@ class LearnerConfig(Config): 'is the epoch number.')) @model_validator(mode='after') - def validate_run_tensorboard(self) -> Self: + def validate_run_tensorboard(self) -> 'Self': if self.run_tensorboard and not self.log_tensorboard: raise ConfigError( 'Cannot run tensorboard if log_tensorboard is False') return self @model_validator(mode='after') - def validate_class_loss_weights(self) -> Self: + def validate_class_loss_weights(self) -> 'Self': if self.solver is None: return self class_loss_weights = self.solver.class_loss_weights diff --git a/rastervision_pytorch_learner/rastervision/pytorch_learner/object_detection_utils.py b/rastervision_pytorch_learner/rastervision/pytorch_learner/object_detection_utils.py index 824217e06..c85885325 100644 --- a/rastervision_pytorch_learner/rastervision/pytorch_learner/object_detection_utils.py +++ b/rastervision_pytorch_learner/rastervision/pytorch_learner/object_detection_utils.py @@ -1,4 +1,4 @@ -from typing import Any, Iterable, Self, Sequence +from typing import TYPE_CHECKING, Any, Iterable, Sequence from collections.abc import Callable from collections import defaultdict from os.path import join @@ -19,8 +19,11 @@ from rastervision.pipeline.file_system import json_to_file, get_tmp_dir from rastervision.pytorch_learner.utils.utils import ONNXRuntimeAdapter +if TYPE_CHECKING: + from typing import Self -def get_coco_gt(targets: Iterable[Self], + +def get_coco_gt(targets: Iterable['Self'], num_class_ids: int) -> dict[str, list[dict]]: images = [] annotations = [] @@ -60,7 +63,7 @@ def get_coco_gt(targets: Iterable[Self], return coco -def get_coco_preds(outputs: Iterable[Self]) -> list[dict]: +def get_coco_preds(outputs: Iterable['Self']) -> list[dict]: preds = [] for img_id, output in enumerate(outputs, 1): boxes = output.convert_boxes('xywh').float().tolist() @@ -157,13 +160,13 @@ def _map_extras( return new_extras - def copy(self) -> Self: + def copy(self) -> 'Self': return BoxList( self.boxes.copy(), **self._map_extras(lambda k, v: v.copy()), cond=lambda k, v: torch.is_tensor(v)) - def to(self, *args, **kwargs) -> Self: + def to(self, *args, **kwargs) -> 'Self': """Recursively apply :meth:`torch.Tensor.to` to Tensors. Args: @@ -190,7 +193,7 @@ def __len__(self) -> int: return len(self.boxes) @staticmethod - def cat(box_lists: Iterable[Self]) -> Self: + def cat(box_lists: Iterable['Self']) -> 'Self': boxes = [] extras = defaultdict(list) for bl in box_lists: @@ -202,7 +205,7 @@ def cat(box_lists: Iterable[Self]) -> Self: extras[k] = torch.cat(v) return BoxList(boxes, **extras) - def equal(self, other: Self) -> bool: + def equal(self, other: 'Self') -> bool: if len(other) != len(self): return False @@ -218,20 +221,20 @@ def equal(self, other: Self) -> bool: other_tups = set([tuple([x.item() for x in row]) for row in cat_arr]) return self_tups == other_tups - def ind_filter(self, inds: Sequence[int]) -> Self: + def ind_filter(self, inds: Sequence[int]) -> 'Self': boxes = self.boxes[inds] extras = self._map_extras( func=lambda k, v: v[inds], cond=lambda k, v: torch.is_tensor(v)) return BoxList(boxes, **extras) - def score_filter(self, score_thresh: float = 0.25) -> Self: + def score_filter(self, score_thresh: float = 0.25) -> 'Self': scores = self.extras.get('scores') if scores is not None: return self.ind_filter(scores > score_thresh) else: raise ValueError('must have scores as key in extras') - def clip_boxes(self, img_height: int, img_width: int) -> Self: + def clip_boxes(self, img_height: int, img_width: int) -> 'Self': boxes = clip_boxes_to_image(self.boxes, (img_height, img_width)) return BoxList(boxes, **self.extras) @@ -243,7 +246,7 @@ def nms(self, iou_thresh: float = 0.5) -> torch.Tensor: self.get_field('class_ids'), iou_thresh) return self.ind_filter(good_inds) - def scale(self, yscale: float, xscale: float) -> Self: + def scale(self, yscale: float, xscale: float) -> 'Self': """Scale box coords by the given scaling factors.""" dtype = self.boxes.dtype boxes = self.boxes.float() @@ -252,7 +255,7 @@ def scale(self, yscale: float, xscale: float) -> Self: self.boxes = boxes.to(dtype=dtype) return self - def pin_memory(self) -> Self: + def pin_memory(self) -> 'Self': self.boxes = self.boxes.pin_memory() for k, v in self.extras.items(): if torch.is_tensor(v): diff --git a/rastervision_pytorch_learner/rastervision/pytorch_learner/utils/utils.py b/rastervision_pytorch_learner/rastervision/pytorch_learner/utils/utils.py index b7da6c570..357c3301d 100644 --- a/rastervision_pytorch_learner/rastervision/pytorch_learner/utils/utils.py +++ b/rastervision_pytorch_learner/rastervision/pytorch_learner/utils/utils.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Any, Container, Iterable, Self, Sequence +from typing import TYPE_CHECKING, Any, Container, Iterable, Sequence from os.path import basename, join, isfile import logging @@ -17,6 +17,7 @@ upgrade_config) if TYPE_CHECKING: + from typing import Self import onnxruntime as ort from rastervision.pytorch_learner import LearnerConfig @@ -457,7 +458,8 @@ def __init__(self, ort_session: 'ort.InferenceSession') -> None: self.input_key = inputs[0].name @classmethod - def from_file(cls, path: str, providers: list[str] | None = None) -> Self: + def from_file(cls, path: str, + providers: list[str] | None = None) -> 'Self': """Construct from file. Args: diff --git a/requirements-dev.txt b/requirements-dev.txt index 86b962089..c75f89c38 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -6,6 +6,7 @@ unify==0.5 sphinx-autobuild==2021.3.14 seaborn==0.13.2 jupyter==1.0.0 -jupyterlab==4.1.8 +jupyterlab==4.2.5 jupyter_contrib_nbextensions==0.7.0 pystac_client==0.8.3 +awscli==1.33.40 diff --git a/tests/pytorch_learner/dataset/test_dataset.py b/tests/pytorch_learner/dataset/test_dataset.py index a582e1c4c..bf4136660 100644 --- a/tests/pytorch_learner/dataset/test_dataset.py +++ b/tests/pytorch_learner/dataset/test_dataset.py @@ -213,8 +213,9 @@ def test_sample_window_within_aoi(self): ds = RandomWindowGeoDataset( scene, - 10, - (5, 6), + out_size=10, + size_lims=(5, 6), + max_windows=10, within_aoi=True, transform_type=TransformType.noop, ) @@ -222,8 +223,9 @@ def test_sample_window_within_aoi(self): ds = RandomWindowGeoDataset( scene, - 10, - (12, 13), + out_size=10, + size_lims=(12, 13), + max_windows=10, within_aoi=True, transform_type=TransformType.noop, ) @@ -231,8 +233,9 @@ def test_sample_window_within_aoi(self): ds = RandomWindowGeoDataset( scene, - 10, - (12, 13), + out_size=10, + size_lims=(12, 13), + max_windows=10, within_aoi=False, transform_type=TransformType.noop, ) @@ -245,6 +248,7 @@ def test_init_validation(self): args = dict( scene=scene, out_size=10, + max_windows=10, transform_type=TransformType.noop, ) self.assertRaises(ValueError, lambda: RandomWindowGeoDataset(**args)) @@ -255,6 +259,7 @@ def test_init_validation(self): out_size=10, size_lims=(10, 11), h_lims=(10, 11), + max_windows=10, transform_type=TransformType.noop, ) self.assertRaises(ValueError, lambda: RandomWindowGeoDataset(**args)) @@ -266,6 +271,7 @@ def test_init_validation(self): size_lims=(10, 11), h_lims=(10, 11), w_lims=(10, 11), + max_windows=10, transform_type=TransformType.noop, ) self.assertRaises(ValueError, lambda: RandomWindowGeoDataset(**args)) @@ -275,6 +281,7 @@ def test_init_validation(self): scene=scene, out_size=10, w_lims=(10, 11), + max_windows=10, transform_type=TransformType.noop, ) self.assertRaises(ValueError, lambda: RandomWindowGeoDataset(**args)) @@ -284,6 +291,7 @@ def test_init_validation(self): scene, out_size=None, size_lims=(12, 13), + max_windows=10, transform_type=TransformType.noop, ) self.assertFalse(ds.normalize) @@ -295,6 +303,7 @@ def test_init_validation(self): out_size=None, h_lims=(10, 11), w_lims=(10, 11), + max_windows=10, transform_type=TransformType.noop, ) self.assertTupleEqual(ds.padding, (5, 5)) @@ -305,6 +314,7 @@ def test_min_max_size(self): scene, out_size=None, size_lims=(10, 15), + max_windows=10, transform_type=TransformType.noop, ) self.assertTupleEqual(ds.min_size, (10, 10)) @@ -315,6 +325,7 @@ def test_min_max_size(self): out_size=None, h_lims=(10, 15), w_lims=(8, 12), + max_windows=10, transform_type=TransformType.noop, ) self.assertTupleEqual(ds.min_size, (10, 8)) @@ -326,6 +337,7 @@ def test_sample_window_size(self): scene, out_size=None, size_lims=(10, 15), + max_windows=10, transform_type=TransformType.noop, ) sampled_h, sampled_w = ds.sample_window_size() @@ -337,6 +349,7 @@ def test_sample_window_size(self): out_size=None, h_lims=(10, 15), w_lims=(8, 12), + max_windows=10, transform_type=TransformType.noop, ) sampled_h, sampled_w = ds.sample_window_size() @@ -360,6 +373,7 @@ def test_return_window(self): scene, out_size=10, size_lims=(5, 6), + max_windows=10, transform_type=TransformType.noop, return_window=True, ) @@ -376,6 +390,7 @@ def test_triangle_missing(self): scene=scene, out_size=10, size_lims=(5, 6), + max_windows=10, transform_type=TransformType.noop, ) self.assertNoError(lambda: RandomWindowGeoDataset(**args))