diff --git a/estimate_albedo.py b/estimate_albedo.py new file mode 100644 index 0000000..4d67a69 --- /dev/null +++ b/estimate_albedo.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +import argparse +from pathlib import Path + +import numpy as np +from PIL import Image +import matplotlib.pyplot as plt +from mpl_toolkits.axes_grid1 import make_axes_locatable + + +def linearize_image(img_pil): + """Convert a PIL RGB image (sRGB) to linear RGB in [0,1].""" + img_srgb = np.array(img_pil.convert("RGB"), dtype=np.float32) / 255.0 + mask = img_srgb <= 0.04045 + img_lin = np.where(mask, img_srgb / 12.92, ((img_srgb + 0.055) / 1.055) ** 2.4) + return np.clip(img_lin, 0, 1) + + +def luminance(img_lin): + """Compute human-eye weighted luminance (ITU-R BT.709) from linear RGB.""" + Y = 0.2126 * img_lin[..., 0] + 0.7152 * img_lin[..., 1] + 0.0722 * img_lin[..., 2] + return np.clip(Y, 0, 1) + + +def lightness_map(img_pil): + """Compute simple lightness (HSL L) from a PIL RGB image.""" + return linearize_image(img_pil).mean(axis=-1) + + +def value_map(img_pil): + """Compute simple brightness (HSV V) from a PIL RGB image.""" + return linearize_image(img_pil).max(axis=-1) + + +def luminance_map(img_pil): + """Compute human-eye weighted luminance (ITU-R BT.709) from a PIL RGB image.""" + return luminance(linearize_image(img_pil)) + + +def parse_args(): + parser = argparse.ArgumentParser( + description="Run Mask2Former segmentation and save material overlay." + ) + parser.add_argument( + "image_path", + type=Path, + help="Path to input image", + ) + args = parser.parse_args() + + image_path: Path = args.image_path + return image_path + + +def main(): + # Parse args + image_path = parse_args() + + # Open image + image = Image.open(image_path).convert("RGB") + + # Calculate albedo + luminance = luminance_map(image) + albedo = luminance / luminance.max() + + # Create image + fig, ax = plt.subplots(figsize=(12, 12)) + im = ax.imshow(albedo, vmin=0, vmax=1, cmap="pink") + ax.set_title("Luminace as proxy for albedo") + ax.axis("off") + + # Attach colorbar that matches height of axes + divider = make_axes_locatable(ax) + cax = divider.append_axes("right", size="5%", pad=0.05) # adjust size and pad + plt.colorbar(im, cax=cax) + + output_path = Path.cwd() / (image_path.stem + "_albedo.png") + fig.savefig(output_path, bbox_inches="tight") + + +if __name__ == "__main__": + plt.rcParams.update({"font.size": 24}) + main() diff --git a/identify_materials.py b/identify_materials.py index bb345b4..81a9c72 100644 --- a/identify_materials.py +++ b/identify_materials.py @@ -64,7 +64,7 @@ # Label map id2label = { - 0: "Background", + 0: "Background/Unclassified", 1: "Wood/Bamboo", 2: "Ground tile", 3: "Brick", @@ -110,11 +110,81 @@ } +def plot_overlay(image, mask): + fig, ax = plt.subplots(figsize=(12, 12)) + + # Map semantic mask to RGB + h, w = mask.shape + color_mask = np.zeros((h, w, 3), dtype=np.uint8) + for lbl, color in enumerate(label_colors): + color_mask[mask == lbl] = color + + # Overlay + image_np = np.array(image.resize((w, h))) + overlay = (0.5 * image_np + 0.5 * color_mask).astype(np.uint8) + + # Plot + ax.imshow(overlay) + ax.axis("off") + + # Legend + classes = np.insert(np.unique(mask), 0, 0) + legend_handles = [ + Patch(color=np.array(c) / 255.0, label=id2label[i]) + for i, c in enumerate(label_colors) + if i in classes + ] + plt.legend( + handles=legend_handles, + bbox_to_anchor=(1, 1), + loc="upper left", + fontsize="small", + frameon=False, + ) + ax.set_title("Material estimates by BFMS") + return fig, ax + + +def plot_side_by_side(image, mask): + # Map semantic mask to RGB + h, w = mask.shape + color_mask = np.zeros((h, w, 3), dtype=np.uint8) + for lbl, color in enumerate(label_colors): + color_mask[mask == lbl] = color + + fig, axs = plt.subplots(1, 2, figsize=(15, 10)) + axs[0].imshow(image) + axs[1].imshow(color_mask) + + # Legend + classes = np.insert(np.unique(mask), 0, 0) + legend_handles = [ + Patch(color=np.array(c) / 255.0, label=id2label[i]) + for i, c in enumerate(label_colors) + if i in classes + ] + plt.legend( + handles=legend_handles, + bbox_to_anchor=(1.05, 1), + loc="upper left", + fontsize="small", + ) + + for ax in axs: + ax.axis("off") + fig.tight_layout() + return fig, axs + + def main(): parser = argparse.ArgumentParser( description="Run Mask2Former segmentation and save material overlay." ) - parser.add_argument("image_path", type=Path, help="Path to input image") + parser.add_argument( + "image_path", + type=Path, + help="Path to input image", + ) parser.add_argument( "--model_path", type=Path, @@ -160,42 +230,21 @@ def main(): pixel_class_probs = torch.einsum("qc,qhw->chw", class_probs, masks_probs) semantic_mask = torch.argmax(pixel_class_probs, dim=0).cpu().numpy() - # Map semantic mask to RGB - h, w = semantic_mask.shape - color_mask = np.zeros((h, w, 3), dtype=np.uint8) - for lbl, color in enumerate(label_colors): - color_mask[semantic_mask == lbl] = color - - # Overlay - image_np = np.array(image.resize((w, h))) - overlay = (0.5 * image_np + 0.5 * color_mask).astype(np.uint8) - - # Plot - plt.figure(figsize=(12, 12)) - plt.imshow(overlay) - plt.axis("off") - - # Legend - classes = np.unique(semantic_mask) - legend_handles = [ - Patch(color=np.array(c) / 255.0, label=id2label[i]) - for i, c in enumerate(label_colors) - if i in classes - ] - plt.legend( - handles=legend_handles, - bbox_to_anchor=(1.05, 1), - loc="upper left", - fontsize="small", - ) - plt.tight_layout() - - # Output filename - output_filename = f"{image_path.stem}_materials.png" + # Overlay image + fig, ax = plot_overlay(image, semantic_mask) + output_filename = f"{image_path.stem}_materials_overlay.png" output_path = Path.cwd() / output_filename - plt.savefig(output_path) + fig.savefig(output_path, bbox_inches="tight") print(f"Saved overlay to {output_path}") + # Side by side image + fig, axs = plot_side_by_side(image, semantic_mask) + output_filename = f"{image_path.stem}_materials_sidebyside.png" + output_path = Path.cwd() / output_filename + fig.savefig(output_path, bbox_inches="tight") + print(f"Saved side-by-side image to {output_path}") + if __name__ == "__main__": + plt.rcParams.update({"font.size": 24}) main() \ No newline at end of file diff --git a/plot_original.py b/plot_original.py new file mode 100644 index 0000000..cbfc146 --- /dev/null +++ b/plot_original.py @@ -0,0 +1,41 @@ +import argparse +from pathlib import Path +from PIL import Image +from matplotlib import pyplot as plt + + +def parse_args(): + parser = argparse.ArgumentParser( + description="Run Mask2Former segmentation and save material overlay." + ) + parser.add_argument( + "image_path", + type=Path, + help="Path to input image", + ) + args = parser.parse_args() + + image_path: Path = args.image_path + return image_path + + +def main(): + # Parse args + image_path = parse_args() + + # Open image + image = Image.open(image_path).convert("RGB") + + # Create image + fig, ax = plt.subplots(figsize=(12, 12)) + ax.imshow(image) + ax.set_title("Original image") + ax.axis("off") + + output_path = Path.cwd() / (image_path.stem + "_original.png") + fig.savefig(output_path, bbox_inches="tight") + + +if __name__ == "__main__": + plt.rcParams.update({"font.size": 24}) + main() diff --git a/pyproject.toml b/pyproject.toml index dd524e4..7182b86 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,6 +5,7 @@ description = "Add your description here" readme = "README.md" requires-python = ">=3.11" dependencies = [ + "ipython>=9.4.0", "matplotlib>=3.10.5", "safetensors>=0.6.2", "scipy>=1.16.1", diff --git a/segment_image.py b/segment_image.py new file mode 100755 index 0000000..f544308 --- /dev/null +++ b/segment_image.py @@ -0,0 +1,291 @@ +#!/home/peter/urban-m4/streetscapes/.venv/bin/python +import argparse +from pathlib import Path + +import torch +import numpy as np +from PIL import Image +import matplotlib.pyplot as plt +from matplotlib.patches import Patch +import transformers as tform +import sam2.sam2_image_predictor as sam2_pred + +# Configuration +DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu") +BOX_THRESHOLD = 0.3 +TEXT_THRESHOLD = 0.3 + +# Default labels (can be customized) +DEFAULT_LABELS = { + "building": {"window": None, "door": None}, + "road": { + "street": None, + "sidewalk": None, + "pavement": None, + "crosswalk": None, + }, + "vegetation": None, + "car": None, + "truck": None, +} + +# Color palette for visualization +COLORS = np.array( + [ + (255, 0, 0), # Red + (0, 255, 0), # Green + (0, 0, 255), # Blue + (255, 255, 0), # Yellow + (255, 0, 255), # Magenta + (0, 255, 255), # Cyan + (255, 128, 0), # Orange + (128, 0, 255), # Purple + (255, 128, 128), # Light Red + (128, 255, 128), # Light Green + (128, 128, 255), # Light Blue + (255, 255, 128), # Light Yellow + (255, 128, 255), # Light Magenta + (128, 255, 255), # Light Cyan + (128, 128, 128), # Gray + ], + dtype=np.uint8, +) + + +def flatten_labels(labels): + """Convert nested label dictionary to flat list of labels.""" + flat_labels = [] + + def _flatten(tree): + for k, v in tree.items(): + if isinstance(v, dict): + flat_labels.append(k) + _flatten(v) + else: + flat_labels.append(k) + + _flatten(labels) + return flat_labels + + +def create_prompt(labels): + """Create text prompt from labels.""" + flat_labels = flatten_labels(labels) + return " ".join([lbl.strip() + "." for lbl in flat_labels if lbl]) + + +def pad_image(image, target_size=2048): + """Pad image to target size.""" + h, w = image.shape[:2] + pad_h = max(0, target_size - h) + pad_w = max(0, target_size - w) + + if image.ndim == 3: + padded = np.pad( + image, ((0, pad_h), (0, pad_w), (0, 0)), mode="constant", constant_values=0 + ) + else: + padded = np.pad( + image, ((0, pad_h), (0, pad_w)), mode="constant", constant_values=0 + ) + + return padded + + +def unpad_mask(mask, original_shape): + """Remove padding from mask to match original image size.""" + h, w = original_shape + return mask[:h, :w] + + +def plot_overlay(image, masks, labels): + """Plot image with segmentation masks overlaid.""" + fig, ax = plt.subplots(figsize=(12, 12)) + + # Start with original image + overlay = np.array(image) + + # Create legend handles + legend_handles = [] + + # Apply each mask with different color + for i, (inst_id, mask) in enumerate(masks.items()): + color = COLORS[i % len(COLORS)] + label = labels.get(inst_id, f"Object {inst_id}") + + # Create colored overlay + colored_mask = np.zeros_like(overlay) + colored_mask[mask] = color + + # Blend with original image + overlay = np.where( + mask[..., None], + (0.7 * overlay + 0.3 * colored_mask).astype(np.uint8), + overlay, + ) + + # Add to legend + legend_handles.append(Patch(color=color / 255.0, label=label)) + + ax.imshow(overlay) + ax.axis("off") + + # Add legend if there are masks + if legend_handles: + plt.legend( + handles=legend_handles, + bbox_to_anchor=(1, 1), + loc="upper left", + fontsize="small", + frameon=False, + ) + + ax.set_title("Instance segmentation with DinoSAM") + + fig.tight_layout() + return fig, ax + + +def plot_side_by_side(image, masks, labels): + """Plot original image and segmentation side by side.""" + fig, axs = plt.subplots(1, 2, figsize=(15, 10)) + + # Original image + axs[0].imshow(image) + axs[0].set_title("Original Image") + axs[0].axis("off") + + # Segmentation mask + seg_image = np.zeros_like(np.array(image)) + legend_handles = [] + + for i, (inst_id, mask) in enumerate(masks.items()): + color = COLORS[i % len(COLORS)] + label = labels.get(inst_id, f"Object {inst_id}") + + seg_image[mask] = color + legend_handles.append(Patch(color=color / 255.0, label=label)) + + axs[1].imshow(seg_image) + axs[1].set_title("Segmentation") + axs[1].axis("off") + + # Add legend + if legend_handles: + plt.legend( + handles=legend_handles, + bbox_to_anchor=(1, 1), + loc="upper left", + fontsize="small", + frameon=False, + ) + + fig.tight_layout() + return fig, axs + + +def main(): + parser = argparse.ArgumentParser( + description="Run semantic segmentation and save overlay." + ) + parser.add_argument( + "image_path", + type=Path, + help="Path to input image", + ) + args = parser.parse_args() + + image_path = Path(args.image_path) + + # Load models + print("Loading SAM model...") + sam_model = sam2_pred.SAM2ImagePredictor.from_pretrained( + "facebook/sam2.1-hiera-large", device=DEVICE + ) + + print("Loading DINO model...") + dino_processor = tform.AutoProcessor.from_pretrained( + "IDEA-Research/grounding-dino-base" + ) + dino_model = tform.AutoModelForZeroShotObjectDetection.from_pretrained( + "IDEA-Research/grounding-dino-base" + ).to(DEVICE) + dino_model.eval() + + # Load and prepare image + print(f"Loading image: {image_path}") + image = Image.open(image_path).convert("RGB") + image_np = np.array(image) + original_shape = image_np.shape[:2] + + # Pad image for processing + padded_image = pad_image(image_np) + + # Create prompt from labels + prompt = create_prompt(DEFAULT_LABELS) + print(f"Using prompt: {prompt}") + + # Run object detection + print("Running object detection...") + inputs = dino_processor( + images=[padded_image], text=[prompt], return_tensors="pt" + ).to(DEVICE) + + with torch.no_grad(): + outputs = dino_model(**inputs) + + results = dino_processor.post_process_grounded_object_detection( + outputs, + inputs["input_ids"], + box_threshold=BOX_THRESHOLD, + text_threshold=TEXT_THRESHOLD, + target_sizes=[padded_image.shape[:2]], + )[0] + + print(f"Detected {len(results['boxes'])} objects") + + # Run segmentation if objects detected + if len(results["boxes"]) > 0: + print("Running segmentation...") + boxes = results["boxes"].cpu().numpy() + + sam_model.set_image(padded_image) + masks, _, _ = sam_model.predict(box=boxes, multimask_output=False) + + # Process masks + masks = np.squeeze(masks, axis=1) # Remove extra dimension + + # Create final masks and labels (merged by class) + merged_masks = {} + for label, mask in zip(results["labels"], masks): + # Remove padding + mask = unpad_mask(mask, original_shape) + mask = mask > 0 # convert to boolean + + # Merge into existing class mask + if label in merged_masks: + merged_masks[label] = merged_masks[label] | mask + else: + merged_masks[label] = mask + + print(f"Generated {len(merged_masks)} class-level segmentation masks") + + # Create visualizations + print("Creating overlay...") + fig, ax = plot_overlay(image, merged_masks, {k: k for k in merged_masks}) + output_filename = f"{image_path.stem}_segmentation_overlay.png" + output_path = Path.cwd() / output_filename + fig.savefig(output_path, bbox_inches="tight") + print(f"Saved overlay to {output_path}") + + print("Creating side-by-side comparison...") + fig, axs = plot_side_by_side(image, merged_masks, {k: k for k in merged_masks}) + output_filename = f"{image_path.stem}_segmentation_sidebyside.png" + output_path = Path.cwd() / output_filename + fig.savefig(output_path, bbox_inches="tight") + print(f"Saved side-by-side image to {output_path}") + + +if __name__ == "__main__": + plt.rcParams.update({"font.size": 24}) + main() diff --git a/uv.lock b/uv.lock index fa26275..8f8e827 100644 --- a/uv.lock +++ b/uv.lock @@ -5,11 +5,21 @@ resolution-markers = [ "python_full_version < '3.12'", ] +[[package]] +name = "asttokens" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4a/e7/82da0a03e7ba5141f05cce0d302e6eed121ae055e0456ca228bf693984bc/asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7", size = 61978 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2", size = 26918 }, +] + [[package]] name = "bfms" version = "0.1.0" source = { virtual = "." } dependencies = [ + { name = "ipython" }, { name = "matplotlib" }, { name = "safetensors" }, { name = "scipy" }, @@ -20,6 +30,7 @@ dependencies = [ [package.metadata] requires-dist = [ + { name = "ipython", specifier = ">=9.4.0" }, { name = "matplotlib", specifier = ">=3.10.5" }, { name = "safetensors", specifier = ">=0.6.2" }, { name = "scipy", specifier = ">=1.16.1" }, @@ -190,6 +201,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321 }, ] +[[package]] +name = "decorator" +version = "5.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190 }, +] + +[[package]] +name = "executing" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/91/50/a9d80c47ff289c611ff12e63f7c5d13942c65d68125160cefd768c73e6e4/executing-2.2.0.tar.gz", hash = "sha256:5d108c028108fe2551d1a7b2e8b713341e2cb4fc0aa7dcf966fa4327a5226755", size = 978693 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/8f/c4d9bafc34ad7ad5d8dc16dd1347ee0e507a52c3adb6bfa8887e1c6a26ba/executing-2.2.0-py2.py3-none-any.whl", hash = "sha256:11387150cad388d62750327a53d3339fad4888b39a6fe233c3afbb54ecffd3aa", size = 26702 }, +] + [[package]] name = "filelock" version = "3.19.1" @@ -300,6 +329,52 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, ] +[[package]] +name = "ipython" +version = "9.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "decorator" }, + { name = "ipython-pygments-lexers" }, + { name = "jedi" }, + { name = "matplotlib-inline" }, + { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit" }, + { name = "pygments" }, + { name = "stack-data" }, + { name = "traitlets" }, + { name = "typing-extensions", marker = "python_full_version < '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/54/80/406f9e3bde1c1fd9bf5a0be9d090f8ae623e401b7670d8f6fdf2ab679891/ipython-9.4.0.tar.gz", hash = "sha256:c033c6d4e7914c3d9768aabe76bbe87ba1dc66a92a05db6bfa1125d81f2ee270", size = 4385338 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/63/f8/0031ee2b906a15a33d6bfc12dd09c3dfa966b3cb5b284ecfb7549e6ac3c4/ipython-9.4.0-py3-none-any.whl", hash = "sha256:25850f025a446d9b359e8d296ba175a36aedd32e83ca9b5060430fe16801f066", size = 611021 }, +] + +[[package]] +name = "ipython-pygments-lexers" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074 }, +] + +[[package]] +name = "jedi" +version = "0.19.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "parso" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278 }, +] + [[package]] name = "jinja2" version = "3.1.6" @@ -514,6 +589,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fc/66/cd29ebc7f6c0d2a15d216fb572573e8fc38bd5d6dec3bd9d7d904c0949f7/matplotlib-3.10.5-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6c49465bf689c4d59d174d0c7795fb42a21d4244d11d70e52b8011987367ac61", size = 8672192 }, ] +[[package]] +name = "matplotlib-inline" +version = "0.1.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/99/5b/a36a337438a14116b16480db471ad061c36c3694df7c2084a0da7ba538b7/matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90", size = 8159 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca", size = 9899 }, +] + [[package]] name = "mpmath" version = "1.3.0" @@ -762,6 +849,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469 }, ] +[[package]] +name = "parso" +version = "0.8.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/66/94/68e2e17afaa9169cf6412ab0f28623903be73d1b32e208d9e8e541bb086d/parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d", size = 400609 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/ac/dac4a63f978e4dcb3c6d3a78c4d8e0192a113d288502a1216950c41b1027/parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18", size = 103650 }, +] + +[[package]] +name = "pexpect" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ptyprocess" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772 }, +] + [[package]] name = "pillow" version = "11.3.0" @@ -846,6 +954,45 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/34/e7/ae39f538fd6844e982063c3a5e4598b8ced43b9633baa3a85ef33af8c05c/pillow-11.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c84d689db21a1c397d001aa08241044aa2069e7587b398c8cc63020390b1c1b8", size = 6984598 }, ] +[[package]] +name = "prompt-toolkit" +version = "3.0.51" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bb/6e/9d084c929dfe9e3bfe0c6a47e31f78a25c54627d64a66e884a8bf5474f1c/prompt_toolkit-3.0.51.tar.gz", hash = "sha256:931a162e3b27fc90c86f1b48bb1fb2c528c2761475e57c9c06de13311c7b54ed", size = 428940 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/4f/5249960887b1fbe561d9ff265496d170b55a735b76724f10ef19f9e40716/prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07", size = 387810 }, +] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993 }, +] + +[[package]] +name = "pure-eval" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842 }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217 }, +] + [[package]] name = "pyparsing" version = "3.2.3" @@ -1086,6 +1233,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, ] +[[package]] +name = "stack-data" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asttokens" }, + { name = "executing" }, + { name = "pure-eval" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521 }, +] + [[package]] name = "sympy" version = "1.14.0" @@ -1210,6 +1371,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540 }, ] +[[package]] +name = "traitlets" +version = "5.14.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/79/72064e6a701c2183016abbbfedaba506d81e30e232a68c9f0d6f6fcd1574/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", size = 161621 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359 }, +] + [[package]] name = "transformers" version = "4.55.2" @@ -1262,3 +1432,12 @@ sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599 wheels = [ { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795 }, ] + +[[package]] +name = "wcwidth" +version = "0.2.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166 }, +]