diff --git a/.gitignore b/.gitignore index e52081a82a..d481899882 100644 --- a/.gitignore +++ b/.gitignore @@ -102,3 +102,4 @@ packages/react-native-executorch/common/rnexecutorch/tests/integration/assets/mo *.tgz Makefile *.pte + diff --git a/apps/computer-vision/app/_layout.tsx b/apps/computer-vision/app/_layout.tsx index cac2692f07..8278b323d9 100644 --- a/apps/computer-vision/app/_layout.tsx +++ b/apps/computer-vision/app/_layout.tsx @@ -84,6 +84,14 @@ export default function _layout() { headerTitleStyle: { color: ColorPalette.primary }, }} /> + Object Detection + router.navigate('instance_segmentation/')} + > + Instance Segmentation + router.navigate('ocr/')} diff --git a/apps/computer-vision/app/instance_segmentation/index.tsx b/apps/computer-vision/app/instance_segmentation/index.tsx new file mode 100644 index 0000000000..e49594eb0a --- /dev/null +++ b/apps/computer-vision/app/instance_segmentation/index.tsx @@ -0,0 +1,277 @@ +import Spinner from '../../components/Spinner'; +import { BottomBar } from '../../components/BottomBar'; +import { getImage } from '../../utils'; +import { useInstanceSegmentation, YOLO26N_SEG } from 'react-native-executorch'; +import { + View, + StyleSheet, + ScrollView, + Text, + TouchableOpacity, +} from 'react-native'; +import React, { useContext, useEffect, useState } from 'react'; +import { GeneratingContext } from '../../context'; +import ScreenWrapper from '../../ScreenWrapper'; +import ImageWithMasks, { + buildDisplayInstances, + DisplayInstance, +} from '../../components/ImageWithMasks'; + +export default function InstanceSegmentationScreen() { + const { setGlobalGenerating } = useContext(GeneratingContext); + + const { + isReady, + isGenerating, + downloadProgress, + forward, + error, + getAvailableInputSizes, + } = useInstanceSegmentation({ + model: YOLO26N_SEG, + }); + + const [imageUri, setImageUri] = useState(''); + const [imageSize, setImageSize] = useState({ width: 0, height: 0 }); + const [instances, setInstances] = useState([]); + const [selectedInputSize, setSelectedInputSize] = useState( + null + ); + + const availableInputSizes = getAvailableInputSizes(); + + useEffect(() => { + setGlobalGenerating(isGenerating); + }, [isGenerating, setGlobalGenerating]); + + // Set default input size when model is ready + useEffect(() => { + if (isReady && availableInputSizes && availableInputSizes.length > 0) { + setSelectedInputSize(availableInputSizes[0]); + } + }, [isReady, availableInputSizes]); + + const handleCameraPress = async (isCamera: boolean) => { + const image = await getImage(isCamera); + if (!image?.uri) return; + setImageUri(image.uri); + setImageSize({ + width: image.width ?? 0, + height: image.height ?? 0, + }); + setInstances([]); + }; + + const runForward = async () => { + if (!imageUri || imageSize.width === 0 || imageSize.height === 0) return; + + try { + const output = await forward(imageUri, { + confidenceThreshold: 0.5, + iouThreshold: 0.55, + maxInstances: 20, + returnMaskAtOriginalResolution: true, + inputSize: selectedInputSize ?? undefined, + }); + + // Convert raw masks → small Skia images immediately. + // Raw Uint8Array mask buffers (backed by native OwningArrayBuffer) + // go out of scope here and become eligible for GC right away. + setInstances(buildDisplayInstances(output)); + } catch (e) { + console.error(e); + } + }; + + if (!isReady && error) { + return ( + + + Error Loading Model + + {error?.message || 'Unknown error occurred'} + + Code: {error?.code || 'N/A'} + + + ); + } + + if (!isReady) { + return ( + + ); + } + + return ( + + + + + + + {imageUri && availableInputSizes && availableInputSizes.length > 0 && ( + + Input Size: + + {availableInputSizes.map((size) => ( + setSelectedInputSize(size)} + > + + {size} + + + ))} + + + )} + + {instances.length > 0 && ( + + + Detected {instances.length} instance(s) + + + {instances.map((instance, idx) => ( + + + {instance.label || 'Unknown'} ( + {(instance.score * 100).toFixed(1)}%) + + + ))} + + + )} + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 6, + width: '100%', + }, + imageContainer: { + flex: 1, + width: '100%', + padding: 16, + }, + inputSizeContainer: { + paddingHorizontal: 16, + paddingVertical: 12, + backgroundColor: '#fff', + borderTopWidth: 1, + borderTopColor: '#e0e0e0', + }, + inputSizeLabel: { + fontSize: 14, + fontWeight: '600', + color: '#333', + marginBottom: 8, + }, + inputSizeScroll: { + flexDirection: 'row', + }, + sizeButton: { + paddingHorizontal: 16, + paddingVertical: 8, + marginRight: 8, + borderRadius: 6, + backgroundColor: '#f0f0f0', + }, + sizeButtonActive: { + backgroundColor: '#007AFF', + }, + sizeButtonText: { + fontSize: 14, + fontWeight: '600', + color: '#666', + }, + sizeButtonTextActive: { + color: '#fff', + }, + resultsContainer: { + maxHeight: 200, + paddingHorizontal: 16, + paddingVertical: 12, + backgroundColor: '#fff', + borderTopWidth: 1, + borderTopColor: '#e0e0e0', + }, + resultsHeader: { + fontSize: 16, + fontWeight: '600', + marginBottom: 8, + color: '#333', + }, + resultsList: { + flex: 1, + }, + resultRow: { + flexDirection: 'row', + alignItems: 'center', + paddingVertical: 6, + paddingHorizontal: 8, + marginBottom: 4, + backgroundColor: '#f9f9f9', + borderRadius: 6, + }, + resultText: { + fontSize: 14, + fontWeight: '500', + color: '#333', + }, + errorContainer: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + padding: 32, + }, + errorTitle: { + fontSize: 20, + fontWeight: '700', + color: '#e74c3c', + marginBottom: 12, + }, + errorText: { + fontSize: 14, + color: '#555', + textAlign: 'center', + marginBottom: 8, + }, + errorCode: { + fontSize: 12, + color: '#999', + fontFamily: 'Courier', + }, +}); diff --git a/apps/computer-vision/app/vision_camera/index.tsx b/apps/computer-vision/app/vision_camera/index.tsx index b2af60d504..c8b93db6ea 100644 --- a/apps/computer-vision/app/vision_camera/index.tsx +++ b/apps/computer-vision/app/vision_camera/index.tsx @@ -31,8 +31,13 @@ import ColorPalette from '../../colors'; import ClassificationTask from '../../components/vision_camera/tasks/ClassificationTask'; import ObjectDetectionTask from '../../components/vision_camera/tasks/ObjectDetectionTask'; import SegmentationTask from '../../components/vision_camera/tasks/SegmentationTask'; +import InstanceSegmentationTask from '../../components/vision_camera/tasks/InstanceSegmentationTask'; -type TaskId = 'classification' | 'objectDetection' | 'segmentation'; +type TaskId = + | 'classification' + | 'objectDetection' + | 'segmentation' + | 'instanceSegmentation'; type ModelId = | 'classification' | 'objectDetectionSsdlite' @@ -43,7 +48,9 @@ type ModelId = | 'segmentationLraspp' | 'segmentationFcnResnet50' | 'segmentationFcnResnet101' - | 'segmentationSelfie'; + | 'segmentationSelfie' + | 'instanceSegmentation_yolo26n' + | 'instanceSegmentation_rfdetr'; type TaskVariant = { id: ModelId; label: string }; type Task = { id: TaskId; label: string; variants: TaskVariant[] }; @@ -67,6 +74,14 @@ const TASKS: Task[] = [ { id: 'segmentationSelfie', label: 'Selfie' }, ], }, + { + id: 'instanceSegmentation', + label: 'Inst Seg', + variants: [ + { id: 'instanceSegmentation_yolo26n', label: 'YOLO26N Seg' }, + { id: 'instanceSegmentation_rfdetr', label: 'RF-DETR Nano Seg' }, + ], + }, { id: 'objectDetection', label: 'Detect', @@ -220,6 +235,16 @@ export default function VisionCameraScreen() { } /> )} + {activeTask === 'instanceSegmentation' && ( + + )} {!isReady && ( diff --git a/apps/computer-vision/components/ImageWithMasks.tsx b/apps/computer-vision/components/ImageWithMasks.tsx new file mode 100644 index 0000000000..0403098bd3 --- /dev/null +++ b/apps/computer-vision/components/ImageWithMasks.tsx @@ -0,0 +1,256 @@ +import React, { useState } from 'react'; +import { Image, StyleSheet, View, Text } from 'react-native'; +import { + Canvas, + Image as SkiaImage, + Skia, + AlphaType, + ColorType, + SkImage, + Rect, + Group, +} from '@shopify/react-native-skia'; + +const INSTANCE_COLORS = [ + [255, 87, 51, 180], + [51, 255, 87, 180], + [51, 87, 255, 180], + [255, 51, 246, 180], + [51, 255, 246, 180], + [243, 255, 51, 180], + [141, 51, 255, 180], + [255, 131, 51, 180], + [51, 255, 131, 180], + [131, 51, 255, 180], +]; + +const MAX_MASK_DIM = 256; + +/** Display-only data — no raw mask buffers. */ +export interface DisplayInstance { + bbox: { x1: number; y1: number; x2: number; y2: number }; + label: string; + score: number; + maskImage: SkImage; +} + +/** + * Convert raw segmentation output into lightweight display instances. + * Call this eagerly (in the forward callback) so raw Uint8Array masks + * can be garbage-collected immediately. + */ +export function buildDisplayInstances( + rawInstances: { + bbox: { x1: number; y1: number; x2: number; y2: number }; + mask: Uint8Array; + maskWidth: number; + maskHeight: number; + label: string | number; + score: number; + }[] +): DisplayInstance[] { + return rawInstances + .map((inst, i) => { + const color = INSTANCE_COLORS[i % INSTANCE_COLORS.length]; + const img = createMaskImage( + inst.mask, + inst.maskWidth, + inst.maskHeight, + color + ); + if (!img) return null; + return { + bbox: inst.bbox, + label: String(inst.label), + score: inst.score, + maskImage: img, + }; + }) + .filter((d): d is DisplayInstance => d !== null); +} + +function createMaskImage( + mask: Uint8Array, + srcW: number, + srcH: number, + color: number[] +): SkImage | null { + const downscale = Math.min(1, MAX_MASK_DIM / Math.max(srcW, srcH)); + const dstW = Math.max(1, Math.round(srcW * downscale)); + const dstH = Math.max(1, Math.round(srcH * downscale)); + + const pixels = new Uint8Array(dstW * dstH * 4); + const r = color[0], + g = color[1], + b = color[2], + a = color[3]; + + for (let dy = 0; dy < dstH; dy++) { + const sy = Math.min(Math.floor(dy / downscale), srcH - 1); + for (let dx = 0; dx < dstW; dx++) { + const sx = Math.min(Math.floor(dx / downscale), srcW - 1); + if (mask[sy * srcW + sx] > 0) { + const idx = (dy * dstW + dx) * 4; + pixels[idx] = r; + pixels[idx + 1] = g; + pixels[idx + 2] = b; + pixels[idx + 3] = a; + } + } + } + + const data = Skia.Data.fromBytes(pixels); + const image = Skia.Image.MakeImage( + { + width: dstW, + height: dstH, + alphaType: AlphaType.Premul, + colorType: ColorType.RGBA_8888, + }, + data, + dstW * 4 + ); + data.dispose(); + return image; +} + +interface Props { + imageUri: string; + instances: DisplayInstance[]; + imageWidth: number; + imageHeight: number; +} + +export default function ImageWithMasks({ + imageUri, + instances, + imageWidth, + imageHeight, +}: Props) { + const [layout, setLayout] = useState({ width: 0, height: 0 }); + + const scaleX = layout.width / (imageWidth || 1); + const scaleY = layout.height / (imageHeight || 1); + const scale = Math.min(scaleX, scaleY); + const offsetX = (layout.width - imageWidth * scale) / 2; + const offsetY = (layout.height - imageHeight * scale) / 2; + + return ( + { + const { width, height } = e.nativeEvent.layout; + setLayout({ width, height }); + }} + > + + + {instances.length > 0 && ( + + + {instances.map((inst, idx) => { + const mx = inst.bbox.x1 * scale + offsetX; + const my = inst.bbox.y1 * scale + offsetY; + const mw = (inst.bbox.x2 - inst.bbox.x1) * scale; + const mh = (inst.bbox.y2 - inst.bbox.y1) * scale; + return ( + + ); + })} + + {instances.map((inst, idx) => { + const color = INSTANCE_COLORS[idx % INSTANCE_COLORS.length]; + const bboxX = inst.bbox.x1 * scale + offsetX; + const bboxY = inst.bbox.y1 * scale + offsetY; + const bboxW = (inst.bbox.x2 - inst.bbox.x1) * scale; + const bboxH = (inst.bbox.y2 - inst.bbox.y1) * scale; + + return ( + + + + ); + })} + + + {instances.map((inst, idx) => { + const color = INSTANCE_COLORS[idx % INSTANCE_COLORS.length]; + const bboxX = inst.bbox.x1 * scale + offsetX; + const bboxY = inst.bbox.y1 * scale + offsetY; + + return ( + + + {inst.label || 'Unknown'} {(inst.score * 100).toFixed(0)}% + + + ); + })} + + )} + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + position: 'relative', + }, + image: { + width: '100%', + height: '100%', + }, + overlay: { + ...StyleSheet.absoluteFillObject, + }, + canvas: { + width: '100%', + height: '100%', + }, + labelContainer: { + position: 'absolute', + paddingHorizontal: 6, + paddingVertical: 2, + borderRadius: 4, + }, + labelText: { + color: 'white', + fontSize: 12, + fontWeight: '600', + }, +}); diff --git a/apps/computer-vision/components/vision_camera/tasks/InstanceSegmentationTask.tsx b/apps/computer-vision/components/vision_camera/tasks/InstanceSegmentationTask.tsx new file mode 100644 index 0000000000..3bd1eab329 --- /dev/null +++ b/apps/computer-vision/components/vision_camera/tasks/InstanceSegmentationTask.tsx @@ -0,0 +1,230 @@ +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import { StyleSheet, Text, View } from 'react-native'; +import { Frame, useFrameOutput } from 'react-native-vision-camera'; +import { scheduleOnRN } from 'react-native-worklets'; +import { + SegmentedInstance, + YOLO26N_SEG, + RF_DETR_NANO_SEG, + useInstanceSegmentation, + CocoLabel, + CocoLabelYolo, +} from 'react-native-executorch'; +import { Canvas, Image as SkiaImage } from '@shopify/react-native-skia'; +import { labelColor, labelColorBg } from '../utils/colors'; +import { TaskProps } from './types'; +import { + buildDisplayInstances, + DisplayInstance, +} from '../../../components/ImageWithMasks'; + +type InstSegModelId = + | 'instanceSegmentation_yolo26n' + | 'instanceSegmentation_rfdetr'; + +type Props = TaskProps & { activeModel: InstSegModelId }; + +export default function InstanceSegmentationTask({ + activeModel, + canvasSize, + cameraPosition, + frameKillSwitch, + onFrameOutputChange, + onReadyChange, + onProgressChange, + onGeneratingChange, + onFpsChange, +}: Props) { + const yolo26n = useInstanceSegmentation({ + model: YOLO26N_SEG, + preventLoad: activeModel !== 'instanceSegmentation_yolo26n', + }); + const rfdetr = useInstanceSegmentation({ + model: RF_DETR_NANO_SEG, + preventLoad: activeModel !== 'instanceSegmentation_rfdetr', + }); + + const active = + activeModel === 'instanceSegmentation_yolo26n' ? yolo26n : rfdetr; + + const [instances, setInstances] = useState([]); + const [imageSize, setImageSize] = useState({ width: 1, height: 1 }); + const lastFrameTimeRef = useRef(Date.now()); + + useEffect(() => { + onReadyChange(active.isReady); + }, [active.isReady, onReadyChange]); + + useEffect(() => { + onProgressChange(active.downloadProgress); + }, [active.downloadProgress, onProgressChange]); + + useEffect(() => { + onGeneratingChange(active.isGenerating); + }, [active.isGenerating, onGeneratingChange]); + + const instSegRof = active.runOnFrame; + + const updateInstances = useCallback( + (p: { + results: + | SegmentedInstance[] + | SegmentedInstance[]; + imageWidth: number; + imageHeight: number; + }) => { + const displayInstances = buildDisplayInstances( + p.results.map((inst) => ({ + ...inst, + label: String(inst.label), + })) + ); + setInstances((prev) => { + // Dispose old mask images + prev.forEach((inst) => inst.maskImage.dispose()); + return displayInstances; + }); + setImageSize({ width: p.imageWidth, height: p.imageHeight }); + const now = Date.now(); + const diff = now - lastFrameTimeRef.current; + if (diff > 0) onFpsChange(Math.round(1000 / diff), diff); + lastFrameTimeRef.current = now; + }, + [onFpsChange] + ); + + const frameOutput = useFrameOutput({ + pixelFormat: 'rgb', + dropFramesWhileBusy: true, + onFrame: useCallback( + (frame: Frame) => { + 'worklet'; + if (frameKillSwitch.getDirty()) { + frame.dispose(); + return; + } + try { + if (!instSegRof) return; + const iw = frame.width > frame.height ? frame.height : frame.width; + const ih = frame.width > frame.height ? frame.width : frame.height; + const result = instSegRof(frame, { + confidenceThreshold: 0.5, + iouThreshold: 0.5, + maxInstances: 5, + returnMaskAtOriginalResolution: false, + ...(activeModel === 'instanceSegmentation_yolo26n' && { + inputSize: 384, + }), + }); + if (result) { + scheduleOnRN(updateInstances, { + results: result, + imageWidth: iw, + imageHeight: ih, + }); + } + } catch { + // ignore + } finally { + frame.dispose(); + } + }, + [instSegRof, frameKillSwitch, updateInstances, activeModel] + ), + }); + + useEffect(() => { + onFrameOutputChange(frameOutput); + }, [frameOutput, onFrameOutputChange]); + + const scale = Math.max( + canvasSize.width / imageSize.width, + canvasSize.height / imageSize.height + ); + const offsetX = (canvasSize.width - imageSize.width * scale) / 2; + const offsetY = (canvasSize.height - imageSize.height * scale) / 2; + + return ( + + {/* Render masks */} + + {instances.map((inst, i) => { + const x = inst.bbox.x1 * scale + offsetX; + const y = inst.bbox.y1 * scale + offsetY; + const w = (inst.bbox.x2 - inst.bbox.x1) * scale; + const h = (inst.bbox.y2 - inst.bbox.y1) * scale; + return ( + + ); + })} + + {/* Render bounding boxes */} + {instances.map((inst, i) => { + const left = inst.bbox.x1 * scale + offsetX; + const top = inst.bbox.y1 * scale + offsetY; + const w = (inst.bbox.x2 - inst.bbox.x1) * scale; + const h = (inst.bbox.y2 - inst.bbox.y1) * scale; + const label = String(inst.label); + return ( + + + + {label} {(inst.score * 100).toFixed(1)} + + + + ); + })} + + ); +} + +const styles = StyleSheet.create({ + bbox: { + position: 'absolute', + borderWidth: 2, + borderColor: 'cyan', + borderRadius: 4, + }, + bboxLabel: { + position: 'absolute', + top: -22, + left: -2, + paddingHorizontal: 6, + paddingVertical: 2, + borderRadius: 4, + }, + bboxLabelText: { color: 'white', fontSize: 11, fontWeight: '600' }, +}); diff --git a/docs/docs/02-benchmarks/inference-time.md b/docs/docs/02-benchmarks/inference-time.md index 7e43d7d8a6..6877402b67 100644 --- a/docs/docs/02-benchmarks/inference-time.md +++ b/docs/docs/02-benchmarks/inference-time.md @@ -210,6 +210,23 @@ slower for very large images, which can increase total time. | FCN_RESNET101 (XNNPACK FP32) | 2600 | 3160 | | FCN_RESNET101 (XNNPACK INT8) | 160 | 620 | +## Instance Segmentation + +:::warning +Times presented in the tables are measured as consecutive runs of the model. Initial run times may be up to 2x longer due to model loading and initialization. +::: +:::warning +Times presented in the tables are measured for forward method with input size equal to 512. Other input sizes may yeild slower or faster inference times. +::: + +| Model | Samsung Galaxy S24 (XNNPACK) [ms] | Iphone 17 pro (XNNPACK) [ms] | +| ----------- | --------------------------------- | ---------------------------- | +| YOLO26N_SEG | 92 | 90 | +| YOLO26S_SEG | 220 | 188 | +| YOLO26M_SEG | 570 | 550 | +| YOLO26L_SEG | 680 | 608 | +| YOLO26X_SEG | 1410 | 1338 | + ## Text to image | Model | iPhone 17 Pro (XNNPACK) [ms] | iPhone 16 Pro (XNNPACK) [ms] | iPhone SE 3 (XNNPACK) [ms] | Samsung Galaxy S24 (XNNPACK) [ms] | OnePlus 12 (XNNPACK) [ms] | diff --git a/docs/docs/02-benchmarks/memory-usage.md b/docs/docs/02-benchmarks/memory-usage.md index 0dc59f0fc3..0ad6e7a11d 100644 --- a/docs/docs/02-benchmarks/memory-usage.md +++ b/docs/docs/02-benchmarks/memory-usage.md @@ -167,6 +167,26 @@ with higher resolutions. | ----------------- | ---------------------- | ------------------ | | DEELABV3_RESNET50 | 930 | 660 | +## Instance Segmentation + +:::info +All the below benchmarks were performed on iPhone 17 Pro (iOS) and OnePlus 12 +(Android). +::: + +:::warning +Data presented in the following sections is based on inference with forward_640 method. +::: + +| Model | Android (XNNPACK) [MB] | iOS (XNNPACK) [MB] | +| ---------------- | ---------------------- | ------------------ | +| YOLO26N_SEG | 92 | 668 | +| YOLO26S_SEG | 220 | 712 | +| YOLO26M_SEG | 570 | 815 | +| YOLO26L_SEG | 680 | 1024 | +| YOLO26X_SEG | 1410 | 1450 | +| RF_DETR_NANO_SEG | 620 | 603 | + ## Text to image :::info diff --git a/docs/docs/02-benchmarks/model-size.md b/docs/docs/02-benchmarks/model-size.md index 8dcfbbf45a..38ea9e7a6e 100644 --- a/docs/docs/02-benchmarks/model-size.md +++ b/docs/docs/02-benchmarks/model-size.md @@ -14,6 +14,17 @@ title: Model Size | ------------------------------ | :---------------: | :---------------: | :---------------: | | SSDLITE_320_MOBILENET_V3_LARGE | 13.9 | 15.6 | 8.46 | +## Instance Segmentation + +| Model | XNNPACK [MB] | +| ---------------- | :----------: | +| YOLO26N_SEG | 11.6 | +| YOLO26S_SEG | 42.3 | +| YOLO26M_SEG | 95.4 | +| YOLO26L_SEG | 113 | +| YOLO26X_SEG | 252 | +| RF_DETR_NANO_SEG | 124 | + ## Style Transfer | Model | XNNPACK FP32 [MB] | XNNPACK INT8 [MB] | Core ML FP32 [MB] | Core ML FP16 [MB] | diff --git a/docs/docs/03-hooks/02-computer-vision/useInstanceSegmentation.md b/docs/docs/03-hooks/02-computer-vision/useInstanceSegmentation.md new file mode 100644 index 0000000000..7b1f45037c --- /dev/null +++ b/docs/docs/03-hooks/02-computer-vision/useInstanceSegmentation.md @@ -0,0 +1,128 @@ +--- +title: useInstanceSegmentation +--- + +Instance segmentation is a computer vision technique that detects individual objects within an image and produces a per-pixel segmentation mask for each one. Unlike object detection (which only returns bounding boxes), instance segmentation provides precise object boundaries. React Native ExecuTorch offers a dedicated hook `useInstanceSegmentation` for this task. + +:::warning +It is recommended to use models provided by us, which are available at our [Hugging Face repository](https://huggingface.co/collections/software-mansion/instance-segmentation). +::: + +## API Reference + +- For detailed API Reference for `useInstanceSegmentation` see: [`useInstanceSegmentation` API Reference](../../06-api-reference/functions/useInstanceSegmentation.md). + +## High Level Overview + +```typescript +import { useInstanceSegmentation, YOLO26N_SEG } from 'react-native-executorch'; + +const model = useInstanceSegmentation({ + model: YOLO26N_SEG, +}); + +const imageUri = 'file:///Users/.../photo.jpg'; + +try { + const instances = await model.forward(imageUri); + // instances is an array of SegmentedInstance objects +} catch (error) { + console.error(error); +} +``` + +### Arguments + +`useInstanceSegmentation` takes [`InstanceSegmentationProps`](../../06-api-reference/interfaces/InstanceSegmentationProps.md) that consists of: + +- `model` - An object containing: + - `modelName` - The name of a built-in model. See [`InstanceSegmentationModelName`](../../06-api-reference/type-aliases/InstanceSegmentationModelName.md) for the list of supported models. + - `modelSource` - The location of the model binary (a URL or a bundled resource). +- An optional flag [`preventLoad`](../../06-api-reference/interfaces/InstanceSegmentationProps.md#preventload) which prevents auto-loading of the model. + +The hook is generic over the model config — TypeScript automatically infers the correct label type based on the `modelName` you provide. No explicit generic parameter is needed. + +For more information on loading resources, take a look at [loading models](../../01-fundamentals/02-loading-models.md) page. + +### Returns + +`useInstanceSegmentation` returns an [`InstanceSegmentationType`](../../06-api-reference/interfaces/InstanceSegmentationType.md) object containing: + +- `isReady` - Whether the model is loaded and ready to process images. +- `isGenerating` - Whether the model is currently processing an image. +- `error` - An error object if the model failed to load or encountered a runtime error. +- `downloadProgress` - A value between 0 and 1 representing the download progress of the model binary. +- `forward` - A function to run inference on an image. + +## Running the model + +To run the model, use the [`forward`](../../06-api-reference/interfaces/InstanceSegmentationType.md#forward) method. It accepts two arguments: + +- `imageSource` (required) - The image to process. Can be a remote URL, a local file URI, or a base64-encoded image (whole URI or only raw base64). +- `options` (optional) - An [`InstanceSegmentationOptions`](../../06-api-reference/interfaces/InstanceSegmentationOptions.md) object with the following fields: + - `confidenceThreshold` - Minimum confidence score for including instances. Defaults to the model's configured threshold (typically `0.5`). + - `iouThreshold` - IoU threshold for non-maximum suppression. Defaults to `0.5`. + - `maxInstances` - Maximum number of instances to return. Defaults to `100`. + - `classesOfInterest` - Filter results to include only specific classes (e.g. `['PERSON', 'CAR']`). Use label names from the model's label enum (e.g. [`CocoLabelYolo`](../../06-api-reference/enumerations/CocoLabelYolo.md) for YOLO models). + - `returnMaskAtOriginalResolution` - Whether to resize masks to the original image resolution. Defaults to `true`. + - `inputSize` - Input size for the model (e.g. `384`, `512`, `640`). Must be one of the model's available input sizes. If the model has only one forward method (i.e. no `availableInputSizes` configured), this option is not needed. + +`forward` returns a promise resolving to an array of [`SegmentedInstance`](../../06-api-reference/interfaces/SegmentedInstance.md) objects, each containing: + +- `bbox` - A [`Bbox`](../../06-api-reference/interfaces/Bbox.md) object with `x1`, `y1` (top-left corner) and `x2`, `y2` (bottom-right corner) coordinates in the original image's pixel space. +- `label` - The class name of the detected instance, typed to the label map of the chosen model. +- `score` - The confidence score of the detection, between 0 and 1. +- `mask` - A `Uint8Array` binary mask (0 or 1) representing the instance's segmentation. +- `maskWidth` - Width of the mask array. +- `maskHeight` - Height of the mask array. + +## Example + +```typescript +import { useInstanceSegmentation, YOLO26N_SEG } from 'react-native-executorch'; + +function App() { + const model = useInstanceSegmentation({ + model: YOLO26N_SEG, + }); + + const handleSegment = async () => { + if (!model.isReady) return; + + const imageUri = 'file:///Users/.../photo.jpg'; + + try { + const instances = await model.forward(imageUri, { + confidenceThreshold: 0.5, + inputSize: 640, + }); + + for (const instance of instances) { + console.log('Label:', instance.label); + console.log('Score:', instance.score); + console.log('Bounding box:', instance.bbox); + console.log('Mask size:', instance.maskWidth, 'x', instance.maskHeight); + } + } catch (error) { + console.error(error); + } + }; + + // ... +} +``` + +## Supported models + +:::info +YOLO models use the [`CocoLabelYolo`](../../06-api-reference/enumerations/CocoLabelYolo.md) enum (80 classes, 0-indexed), which differs from [`CocoLabel`](../../06-api-reference/enumerations/CocoLabel.md) used by RF-DETR and SSDLite object detection models (91 classes, 1-indexed). When filtering with `classesOfInterest`, use the label names from `CocoLabelYolo`. +::: + +| Model | Number of classes | Class list | Available input sizes | +| --------------- | ----------------- | ------------------------------------------------------------------- | --------------------- | +| yolo26n-seg | 80 | [COCO (YOLO)](../../06-api-reference/enumerations/CocoLabelYolo.md) | 384, 512, 640 | +| yolo26s-seg | 80 | [COCO (YOLO)](../../06-api-reference/enumerations/CocoLabelYolo.md) | 384, 512, 640 | +| yolo26m-seg | 80 | [COCO (YOLO)](../../06-api-reference/enumerations/CocoLabelYolo.md) | 384, 512, 640 | +| yolo26l-seg | 80 | [COCO (YOLO)](../../06-api-reference/enumerations/CocoLabelYolo.md) | 384, 512, 640 | +| yolo26x-seg | 80 | [COCO (YOLO)](../../06-api-reference/enumerations/CocoLabelYolo.md) | 384, 512, 640 | +| rfdetr-nano-seg | 91 | [COCO](../../06-api-reference/enumerations/CocoLabel.md) | N/A | diff --git a/docs/docs/04-typescript-api/02-computer-vision/InstanceSegmentationModule.md b/docs/docs/04-typescript-api/02-computer-vision/InstanceSegmentationModule.md new file mode 100644 index 0000000000..846330ac52 --- /dev/null +++ b/docs/docs/04-typescript-api/02-computer-vision/InstanceSegmentationModule.md @@ -0,0 +1,126 @@ +--- +title: InstanceSegmentationModule +--- + +TypeScript API implementation of the [useInstanceSegmentation](../../03-hooks/02-computer-vision/useInstanceSegmentation.md) hook. + +## API Reference + +- For detailed API Reference for `InstanceSegmentationModule` see: [`InstanceSegmentationModule` API Reference](../../06-api-reference/classes/InstanceSegmentationModule.md). + +## High Level Overview + +```typescript +import { + InstanceSegmentationModule, + YOLO26N_SEG, +} from 'react-native-executorch'; + +const imageUri = 'path/to/image.png'; + +// Creating an instance from a built-in model +const segmentation = + await InstanceSegmentationModule.fromModelName(YOLO26N_SEG); + +// Running the model +const instances = await segmentation.forward(imageUri); +``` + +### Methods + +All methods of `InstanceSegmentationModule` are explained in details here: [`InstanceSegmentationModule` API Reference](../../06-api-reference/classes/InstanceSegmentationModule.md) + +## Loading the model + +There are two ways to create an `InstanceSegmentationModule`: + +### From a built-in model + +Use [`fromModelName`](../../06-api-reference/classes/InstanceSegmentationModule.md#frommodelname) for pre-configured models. It accepts: + +- `config` - A model configuration object (e.g. `YOLO26N_SEG`, `YOLO26S_SEG`) imported from the library, containing: + - `modelName` - The name of a built-in model. + - `modelSource` - Location of the model binary (a URL or a bundled resource). +- `onDownloadProgress` (optional) - Callback to track download progress, receiving a value between 0 and 1. + +```typescript +import { + InstanceSegmentationModule, + YOLO26N_SEG, +} from 'react-native-executorch'; + +const segmentation = + await InstanceSegmentationModule.fromModelName(YOLO26N_SEG); +``` + +### From a custom config + +Use [`fromCustomConfig`](../../06-api-reference/classes/InstanceSegmentationModule.md#fromcustomconfig) for custom-exported models with your own label map. It accepts: + +- `modelSource` - Location of the model binary. +- `config` - An [`InstanceSegmentationConfig`](../../06-api-reference/type-aliases/InstanceSegmentationConfig.md) object with: + - `labelMap` - An enum-like object mapping class names to indices. + - `preprocessorConfig` (optional) - Normalization parameters (`normMean`, `normStd`). + - `postprocessorConfig` (optional) - Postprocessing settings (`applyNMS`). + - `defaultConfidenceThreshold` (optional) - Default confidence threshold. + - `defaultIouThreshold` (optional) - Default IoU threshold. + - `availableInputSizes` (optional) - Array of supported input sizes (e.g., `[384, 512, 640]`). **Required** if your model exports multiple forward methods. + - `defaultInputSize` (optional) - The input size to use when `options.inputSize` is not provided. **Required** if `availableInputSizes` is specified. +- `onDownloadProgress` (optional) - Callback to track download progress. + +:::tip +If your model supports **multiple input sizes**, you must specify both `availableInputSizes` (an array of supported sizes) and `defaultInputSize` (the default size to use when no `inputSize` is provided in options). The model must expose separate methods named `forward_{inputSize}` for each size. + +If your model supports only **one input size**, omit both fields and export a single `forward` method. +::: + +```typescript +const MyLabels = { GRAPE_GREEN: 0, GRAPE_RED: 1, LEAF: 2 } as const; + +const segmentation = await InstanceSegmentationModule.fromCustomConfig( + 'https://huggingface.co/.../grape_seg.pte', + { + labelMap: MyLabels, + availableInputSizes: [640], + defaultInputSize: 640, + defaultConfidenceThreshold: 0.4, + postprocessorConfig: { applyNMS: true }, + } +); +``` + +For more information on loading resources, take a look at [loading models](../../01-fundamentals/02-loading-models.md) page. + +## Custom model output contract + +If you want to use a custom-exported model, it must conform to the following output contract: + +The model must produce **3 output tensors**: + +| Tensor | Shape | Description | +| ----------- | -------------- | --------------------------------------------------------------- | +| bboxes | `[1, N, 4]` | Bounding boxes as `[x1, y1, x2, y2]` in model input coordinates | +| scores | `[1, N, 2]` | `[max_score, class_id]` — scores must be post-sigmoid | +| mask_logits | `[1, N, H, W]` | Per-detection binary mask logits — pre-sigmoid | + +### Method naming convention + +- If the model supports **multiple input sizes**, it must expose separate methods named `forward_{inputSize}` (e.g. `forward_384`, `forward_512`, `forward_640`). +- If the model supports **only one input size**, it should expose a single `forward` method. + +## Running the model + +To run the model, use the [`forward`](../../06-api-reference/classes/InstanceSegmentationModule.md#forward) method. It accepts two arguments: + +- `imageSource` (required) - The image to process. Can be a remote URL, a local file URI, or a base64-encoded image (whole URI or only raw base64). +- `options` (optional) - An [`InstanceSegmentationOptions`](../../06-api-reference/interfaces/InstanceSegmentationOptions.md) object for configuring the segmentation (confidence threshold, IoU threshold, input size, classes of interest, etc.). + +The method returns a promise resolving to an array of [`SegmentedInstance`](../../06-api-reference/interfaces/SegmentedInstance.md) objects. Each object contains bounding box coordinates, a binary segmentation mask, a string `label` (resolved from the model's label enum), and the confidence score. + +:::info +Built-in YOLO models use [`CocoLabelYolo`](../../06-api-reference/enumerations/CocoLabelYolo.md) (80 classes, 0-indexed), not [`CocoLabel`](../../06-api-reference/enumerations/CocoLabel.md) (91 classes, 1-indexed, used by RF-DETR / SSDLite). When filtering with `classesOfInterest`, use the key names from `CocoLabelYolo`. +::: + +## Managing memory + +The module is a regular JavaScript object, and as such its lifespan will be managed by the garbage collector. In most cases this should be enough, and you should not worry about freeing the memory of the module yourself, but in some cases you may want to release the memory occupied by the module before the garbage collector steps in. In this case use the method [`delete`](../../06-api-reference/classes/InstanceSegmentationModule.md#delete) on the module object you will no longer use, and want to remove from the memory. Note that you cannot use [`forward`](../../06-api-reference/classes/InstanceSegmentationModule.md#forward) after [`delete`](../../06-api-reference/classes/InstanceSegmentationModule.md#delete) unless you load the module again. diff --git a/packages/react-native-executorch/common/rnexecutorch/RnExecutorchInstaller.cpp b/packages/react-native-executorch/common/rnexecutorch/RnExecutorchInstaller.cpp index 9d4b419e28..1239642c5e 100644 --- a/packages/react-native-executorch/common/rnexecutorch/RnExecutorchInstaller.cpp +++ b/packages/react-native-executorch/common/rnexecutorch/RnExecutorchInstaller.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -47,6 +48,12 @@ void RnExecutorchInstaller::injectJSIBindings( models::semantic_segmentation::BaseSemanticSegmentation>( jsiRuntime, jsCallInvoker, "loadSemanticSegmentation")); + jsiRuntime->global().setProperty( + *jsiRuntime, "loadInstanceSegmentation", + RnExecutorchInstaller::loadModel< + models::instance_segmentation::BaseInstanceSegmentation>( + jsiRuntime, jsCallInvoker, "loadInstanceSegmentation")); + jsiRuntime->global().setProperty( *jsiRuntime, "loadTextToImage", RnExecutorchInstaller::loadModel( diff --git a/packages/react-native-executorch/common/rnexecutorch/data_processing/ImageProcessing.cpp b/packages/react-native-executorch/common/rnexecutorch/data_processing/ImageProcessing.cpp index d7a763819a..3e73a3d8a4 100644 --- a/packages/react-native-executorch/common/rnexecutorch/data_processing/ImageProcessing.cpp +++ b/packages/react-native-executorch/common/rnexecutorch/data_processing/ImageProcessing.cpp @@ -249,5 +249,13 @@ readImageToTensor(const std::string &path, } return {image_processing::getTensorFromMatrix(tensorDims, input), imageSize}; } + +cv::Mat applySigmoid(const cv::Mat &logits) { + cv::Mat probMat; + cv::exp(-logits, probMat); + probMat = 255.0f / (1.0f + probMat); + probMat.convertTo(probMat, CV_8UC1); + return probMat; +} } // namespace image_processing } // namespace rnexecutorch diff --git a/packages/react-native-executorch/common/rnexecutorch/data_processing/ImageProcessing.h b/packages/react-native-executorch/common/rnexecutorch/data_processing/ImageProcessing.h index 1b0c10b333..8b371f87e3 100644 --- a/packages/react-native-executorch/common/rnexecutorch/data_processing/ImageProcessing.h +++ b/packages/react-native-executorch/common/rnexecutorch/data_processing/ImageProcessing.h @@ -54,4 +54,10 @@ readImageToTensor(const std::string &path, bool maintainAspectRatio = false, std::optional normMean = std::nullopt, std::optional normStd = std::nullopt); +/** + * @brief Applies sigmoid activation to logits and converts to uint8 binary mask + * @param logits Input matrix containing raw logits (pre-sigmoid) + * @return 8-bit unsigned integer matrix with values scaled to [0, 255] + */ +cv::Mat applySigmoid(const cv::Mat &logits); } // namespace rnexecutorch::image_processing diff --git a/packages/react-native-executorch/common/rnexecutorch/host_objects/JsiConversions.h b/packages/react-native-executorch/common/rnexecutorch/host_objects/JsiConversions.h index a4e373c2b8..7b389d45b6 100644 --- a/packages/react-native-executorch/common/rnexecutorch/host_objects/JsiConversions.h +++ b/packages/react-native-executorch/common/rnexecutorch/host_objects/JsiConversions.h @@ -16,6 +16,8 @@ #include #include +#include +#include #include #include #include @@ -23,6 +25,7 @@ #include #include #include +#include using namespace rnexecutorch::models::speech_to_text; @@ -430,19 +433,24 @@ getJsiValue(const std::unordered_map &map, return mapObj; } +inline jsi::Value getJsiValue(const utils::computer_vision::BBox &bbox, + jsi::Runtime &runtime) { + jsi::Object obj(runtime); + obj.setProperty(runtime, "x1", bbox.x1); + obj.setProperty(runtime, "y1", bbox.y1); + obj.setProperty(runtime, "x2", bbox.x2); + obj.setProperty(runtime, "y2", bbox.y2); + return obj; +} + inline jsi::Value getJsiValue( const std::vector &detections, jsi::Runtime &runtime) { jsi::Array array(runtime, detections.size()); for (std::size_t i = 0; i < detections.size(); ++i) { jsi::Object detection(runtime); - jsi::Object bbox(runtime); - bbox.setProperty(runtime, "x1", detections[i].x1); - bbox.setProperty(runtime, "y1", detections[i].y1); - bbox.setProperty(runtime, "x2", detections[i].x2); - bbox.setProperty(runtime, "y2", detections[i].y2); - - detection.setProperty(runtime, "bbox", bbox); + detection.setProperty(runtime, "bbox", + getJsiValue(detections[i].bbox, runtime)); detection.setProperty( runtime, "label", jsi::String::createFromUtf8(runtime, detections[i].label)); @@ -452,6 +460,37 @@ inline jsi::Value getJsiValue( return array; } +inline jsi::Value +getJsiValue(const std::vector + &instances, + jsi::Runtime &runtime) { + jsi::Array array(runtime, instances.size()); + for (std::size_t i = 0; i < instances.size(); ++i) { + jsi::Object instance(runtime); + + instance.setProperty(runtime, "bbox", + getJsiValue(instances[i].bbox, runtime)); + + // Mask as Uint8Array - reuse existing OwningArrayBuffer + jsi::ArrayBuffer arrayBuffer(runtime, instances[i].mask); + auto uint8ArrayCtor = + runtime.global().getPropertyAsFunction(runtime, "Uint8Array"); + auto uint8Array = uint8ArrayCtor.callAsConstructor(runtime, arrayBuffer) + .getObject(runtime); + instance.setProperty(runtime, "mask", uint8Array); + + instance.setProperty(runtime, "maskWidth", instances[i].maskWidth); + instance.setProperty(runtime, "maskHeight", instances[i].maskHeight); + + instance.setProperty(runtime, "classIndex", instances[i].classIndex); + + instance.setProperty(runtime, "score", instances[i].score); + + array.setValueAtIndex(runtime, i, instance); + } + return array; +} + inline jsi::Value getJsiValue(const std::vector &detections, jsi::Runtime &runtime) { diff --git a/packages/react-native-executorch/common/rnexecutorch/models/VisionModel.h b/packages/react-native-executorch/common/rnexecutorch/models/VisionModel.h index 6f9a9532f4..4ce9ee779f 100644 --- a/packages/react-native-executorch/common/rnexecutorch/models/VisionModel.h +++ b/packages/react-native-executorch/common/rnexecutorch/models/VisionModel.h @@ -90,9 +90,14 @@ class VisionModel : public BaseModel { */ virtual cv::Mat preprocess(const cv::Mat &image) const; - /// Convenience accessor: spatial dimensions of the model input. - cv::Size modelInputSize() const; - + /** + * @brief Get the spatial dimensions of the model input. + * + * By default, returns the last two dimensions of modelInputShape_. + * Subclasses may override this for models with dynamic or multiple input + * sizes. + */ + virtual cv::Size modelInputSize() const; /** * @brief Extract an RGB cv::Mat from a VisionCamera frame * diff --git a/packages/react-native-executorch/common/rnexecutorch/models/instance_segmentation/BaseInstanceSegmentation.cpp b/packages/react-native-executorch/common/rnexecutorch/models/instance_segmentation/BaseInstanceSegmentation.cpp new file mode 100644 index 0000000000..9f62ad5a8c --- /dev/null +++ b/packages/react-native-executorch/common/rnexecutorch/models/instance_segmentation/BaseInstanceSegmentation.cpp @@ -0,0 +1,358 @@ +#include "BaseInstanceSegmentation.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace rnexecutorch::models::instance_segmentation { + +BaseInstanceSegmentation::BaseInstanceSegmentation( + const std::string &modelSource, std::vector normMean, + std::vector normStd, bool applyNMS, + std::shared_ptr callInvoker) + : VisionModel(modelSource, callInvoker), applyNMS_(applyNMS) { + + if (normMean.size() == 3) { + normMean_ = cv::Scalar(normMean[0], normMean[1], normMean[2]); + } else if (!normMean.empty()) { + log(LOG_LEVEL::Warn, + "normMean must have 3 elements — ignoring provided value."); + } + if (normStd.size() == 3) { + normStd_ = cv::Scalar(normStd[0], normStd[1], normStd[2]); + } else if (!normStd.empty()) { + log(LOG_LEVEL::Warn, + "normStd must have 3 elements — ignoring provided value."); + } +} + +cv::Size BaseInstanceSegmentation::modelInputSize() const { + if (currentlyLoadedMethod_.empty()) { + return VisionModel::modelInputSize(); + } + auto inputShapes = getAllInputShapes(currentlyLoadedMethod_); + if (inputShapes.empty() || inputShapes[0].size() < 2) { + return VisionModel::modelInputSize(); + } + const auto &shape = inputShapes[0]; + return {shape[shape.size() - 2], shape[shape.size() - 1]}; +} + +TensorPtr BaseInstanceSegmentation::buildInputTensor(const cv::Mat &image) { + cv::Mat preprocessed = preprocess(image); + return (normMean_.has_value() && normStd_.has_value()) + ? image_processing::getTensorFromMatrix( + modelInputShape_, preprocessed, normMean_.value(), + normStd_.value()) + : image_processing::getTensorFromMatrix(modelInputShape_, + preprocessed); +} + +std::vector BaseInstanceSegmentation::runInference( + const cv::Mat &image, double confidenceThreshold, double iouThreshold, + int32_t maxInstances, const std::vector &classIndices, + bool returnMaskAtOriginalResolution, const std::string &methodName) { + + std::scoped_lock lock(inference_mutex_); + + ensureMethodLoaded(methodName); + + auto inputShapes = getAllInputShapes(methodName); + if (inputShapes.empty() || inputShapes[0].empty()) { + throw RnExecutorchError(RnExecutorchErrorCode::UnexpectedNumInputs, + "Method '" + methodName + + "' has invalid input tensor shape."); + } + + modelInputShape_ = inputShapes[0]; + const auto &shape = modelInputShape_; + cv::Size modelInputSize(shape[shape.size() - 2], shape[shape.size() - 1]); + cv::Size originalSize(image.cols, image.rows); + + auto forwardResult = + BaseModel::execute(methodName, {buildInputTensor(image)}); + if (!forwardResult.ok()) { + throw RnExecutorchError( + forwardResult.error(), + "The model's forward function did not succeed. " + "Ensure the model input is correct and method name '" + + methodName + "' is valid."); + } + + validateThresholds(confidenceThreshold, iouThreshold); + validateOutputTensors(forwardResult.get()); + + auto instances = collectInstances( + forwardResult.get(), originalSize, modelInputSize, confidenceThreshold, + classIndices, returnMaskAtOriginalResolution); + return finalizeInstances(std::move(instances), iouThreshold, maxInstances); +} + +std::vector BaseInstanceSegmentation::generateFromString( + std::string imageSource, double confidenceThreshold, double iouThreshold, + int32_t maxInstances, std::vector classIndices, + bool returnMaskAtOriginalResolution, std::string methodName) { + + cv::Mat imageBGR = image_processing::readImage(imageSource); + cv::Mat imageRGB; + cv::cvtColor(imageBGR, imageRGB, cv::COLOR_BGR2RGB); + + return runInference(imageRGB, confidenceThreshold, iouThreshold, maxInstances, + classIndices, returnMaskAtOriginalResolution, methodName); +} + +std::vector BaseInstanceSegmentation::generateFromFrame( + jsi::Runtime &runtime, const jsi::Value &frameData, + double confidenceThreshold, double iouThreshold, int32_t maxInstances, + std::vector classIndices, bool returnMaskAtOriginalResolution, + std::string methodName) { + + cv::Mat frame = extractFromFrame(runtime, frameData); + return runInference(frame, confidenceThreshold, iouThreshold, maxInstances, + classIndices, returnMaskAtOriginalResolution, methodName); +} + +std::vector BaseInstanceSegmentation::generateFromPixels( + JSTensorViewIn tensorView, double confidenceThreshold, double iouThreshold, + int32_t maxInstances, std::vector classIndices, + bool returnMaskAtOriginalResolution, std::string methodName) { + + cv::Mat image = extractFromPixels(tensorView); + return runInference(image, confidenceThreshold, iouThreshold, maxInstances, + classIndices, returnMaskAtOriginalResolution, methodName); +} + +std::tuple +BaseInstanceSegmentation::extractDetectionData(const float *bboxData, + const float *scoresData, + int32_t index) { + utils::computer_vision::BBox bbox{ + bboxData[index * 4], bboxData[index * 4 + 1], bboxData[index * 4 + 2], + bboxData[index * 4 + 3]}; + float score = scoresData[index * 2]; + int32_t label = static_cast(scoresData[index * 2 + 1]); + + return {bbox, score, label}; +} + +cv::Rect BaseInstanceSegmentation::computeMaskCropRect( + const utils::computer_vision::BBox &bboxModel, cv::Size modelInputSize, + cv::Size maskSize) { + + float mx1F = bboxModel.x1 * maskSize.width / modelInputSize.width; + float my1F = bboxModel.y1 * maskSize.height / modelInputSize.height; + float mx2F = bboxModel.x2 * maskSize.width / modelInputSize.width; + float my2F = bboxModel.y2 * maskSize.height / modelInputSize.height; + + int32_t mx1 = std::max(0, static_cast(std::floor(mx1F))); + int32_t my1 = std::max(0, static_cast(std::floor(my1F))); + int32_t mx2 = std::min(maskSize.width, static_cast(std::ceil(mx2F))); + int32_t my2 = + std::min(maskSize.height, static_cast(std::ceil(my2F))); + + return {mx1, my1, mx2 - mx1, my2 - my1}; +} + +cv::Rect BaseInstanceSegmentation::addPaddingToRect(const cv::Rect &rect, + cv::Size maskSize) { + int32_t x1 = std::max(0, rect.x - 1); + int32_t y1 = std::max(0, rect.y - 1); + int32_t x2 = std::min(maskSize.width, rect.x + rect.width + 1); + int32_t y2 = std::min(maskSize.height, rect.y + rect.height + 1); + + return {x1, y1, x2 - x1, y2 - y1}; +} + +cv::Mat BaseInstanceSegmentation::warpToOriginalResolution( + const cv::Mat &probMat, const cv::Rect &maskRect, cv::Size originalSize, + cv::Size maskSize, const utils::computer_vision::BBox &bboxOriginal) { + + float scaleX = static_cast(originalSize.width) / maskSize.width; + float scaleY = static_cast(originalSize.height) / maskSize.height; + + cv::Mat M = (cv::Mat_(2, 3) << scaleX, 0, + (maskRect.x * scaleX - bboxOriginal.x1), 0, scaleY, + (maskRect.y * scaleY - bboxOriginal.y1)); + + cv::Size bboxSize(static_cast(std::round(bboxOriginal.width())), + static_cast(std::round(bboxOriginal.height()))); + + cv::Mat warped; + cv::warpAffine(probMat, warped, M, bboxSize, cv::INTER_LINEAR); + return warped; +} + +cv::Mat BaseInstanceSegmentation::thresholdToBinary(const cv::Mat &probMat) { + cv::Mat binary; + cv::threshold(probMat, binary, 127, 1, cv::THRESH_BINARY); + return binary; +} + +cv::Mat BaseInstanceSegmentation::processMaskFromLogits( + const cv::Mat &logitsMat, const utils::computer_vision::BBox &bboxModel, + const utils::computer_vision::BBox &bboxOriginal, cv::Size modelInputSize, + cv::Size originalSize, bool warpToOriginal) { + + cv::Size maskSize = logitsMat.size(); + cv::Rect cropRect = computeMaskCropRect(bboxModel, modelInputSize, maskSize); + + if (warpToOriginal) { + cropRect = addPaddingToRect(cropRect, maskSize); + } + + cv::Mat cropped = logitsMat(cropRect); + cv::Mat probMat = image_processing::applySigmoid(cropped); + + if (warpToOriginal) { + probMat = warpToOriginalResolution(probMat, cropRect, originalSize, + maskSize, bboxOriginal); + } + return thresholdToBinary(probMat); +} + +void BaseInstanceSegmentation::validateThresholds(double confidenceThreshold, + double iouThreshold) const { + if (confidenceThreshold < 0 || confidenceThreshold > 1) { + throw RnExecutorchError( + RnExecutorchErrorCode::InvalidConfig, + "Confidence threshold must be greater or equal to 0 " + "and less than or equal to 1."); + } + + if (iouThreshold < 0 || iouThreshold > 1) { + throw RnExecutorchError(RnExecutorchErrorCode::InvalidConfig, + "IoU threshold must be greater or equal to 0 " + "and less than or equal to 1."); + } +} + +void BaseInstanceSegmentation::validateOutputTensors( + const std::vector &tensors) const { + if (tensors.size() != 3) { + throw RnExecutorchError(RnExecutorchErrorCode::InvalidModelOutput, + "Expected 3 output tensors ([1,N,4] + [1,N,2] + " + "[1,N,H,W]), got " + + std::to_string(tensors.size())); + } +} + +std::set BaseInstanceSegmentation::prepareAllowedClasses( + const std::vector &classIndices) const { + std::set allowedClasses; + if (!classIndices.empty()) { + allowedClasses.insert(classIndices.begin(), classIndices.end()); + } + return allowedClasses; +} + +void BaseInstanceSegmentation::ensureMethodLoaded( + const std::string &methodName) { + if (methodName.empty()) { + throw RnExecutorchError( + RnExecutorchErrorCode::InvalidConfig, + "methodName cannot be empty. Use 'forward' for single-method models " + "or 'forward_{inputSize}' for multi-method models."); + } + + if (!module_) { + throw RnExecutorchError(RnExecutorchErrorCode::ModuleNotLoaded, + "Model not loaded: Cannot load method '" + + methodName + "'"); + } + + if (!currentlyLoadedMethod_.empty()) { + module_->unload_method(currentlyLoadedMethod_); + } + auto loadResult = module_->load_method(methodName); + if (loadResult != executorch::runtime::Error::Ok) { + throw RnExecutorchError( + loadResult, "Failed to load method '" + methodName + + "'. Ensure the method exists in the exported model."); + } + currentlyLoadedMethod_ = methodName; +} + +std::vector BaseInstanceSegmentation::finalizeInstances( + std::vector instances, double iouThreshold, + int32_t maxInstances) const { + + if (applyNMS_) { + instances = + utils::computer_vision::nonMaxSuppression(instances, iouThreshold); + } + + if (std::cmp_greater(instances.size(), maxInstances)) { + instances.resize(maxInstances); + } + + return instances; +} + +std::vector BaseInstanceSegmentation::collectInstances( + const std::vector &tensors, cv::Size originalSize, + cv::Size modelInputSize, double confidenceThreshold, + const std::vector &classIndices, + bool returnMaskAtOriginalResolution) { + + float widthRatio = + static_cast(originalSize.width) / modelInputSize.width; + float heightRatio = + static_cast(originalSize.height) / modelInputSize.height; + auto allowedClasses = prepareAllowedClasses(classIndices); + + // CONTRACT + auto bboxTensor = tensors[0].toTensor(); // [1, N, 4] + auto scoresTensor = tensors[1].toTensor(); // [1, N, 2] + auto maskTensor = tensors[2].toTensor(); // [1, N, H, W] + + int32_t numInstances = bboxTensor.size(1); + int32_t maskH = maskTensor.size(2); + int32_t maskW = maskTensor.size(3); + + const float *bboxData = bboxTensor.const_data_ptr(); + const float *scoresData = scoresTensor.const_data_ptr(); + const float *maskData = maskTensor.const_data_ptr(); + + auto isValidDetection = + [&allowedClasses, &confidenceThreshold](float score, int32_t labelIdx) { + return score >= confidenceThreshold && + (allowedClasses.empty() || allowedClasses.count(labelIdx) != 0); + }; + + std::vector instances; + + for (int32_t i = 0; i < numInstances; ++i) { + auto [bboxModel, score, labelIdx] = + extractDetectionData(bboxData, scoresData, i); + + if (!isValidDetection(score, labelIdx)) { + continue; + } + + utils::computer_vision::BBox bboxOriginal = + bboxModel.scale(widthRatio, heightRatio); + if (!bboxOriginal.isValid()) { + continue; + } + + cv::Mat logitsMat(maskH, maskW, CV_32FC1, + const_cast(maskData + (i * maskH * maskW))); + + cv::Mat binaryMask = processMaskFromLogits( + logitsMat, bboxModel, bboxOriginal, modelInputSize, originalSize, + returnMaskAtOriginalResolution); + + instances.emplace_back(bboxOriginal, + std::make_shared( + binaryMask.data, binaryMask.total()), + binaryMask.cols, binaryMask.rows, labelIdx, score); + } + + return instances; +} + +} // namespace rnexecutorch::models::instance_segmentation diff --git a/packages/react-native-executorch/common/rnexecutorch/models/instance_segmentation/BaseInstanceSegmentation.h b/packages/react-native-executorch/common/rnexecutorch/models/instance_segmentation/BaseInstanceSegmentation.h new file mode 100644 index 0000000000..341d0f2235 --- /dev/null +++ b/packages/react-native-executorch/common/rnexecutorch/models/instance_segmentation/BaseInstanceSegmentation.h @@ -0,0 +1,109 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "Types.h" +#include "rnexecutorch/metaprogramming/ConstructorHelpers.h" +#include +#include + +namespace rnexecutorch { +namespace models::instance_segmentation { +using executorch::extension::TensorPtr; +using executorch::runtime::EValue; + +class BaseInstanceSegmentation : public VisionModel { +public: + BaseInstanceSegmentation(const std::string &modelSource, + std::vector normMean, + std::vector normStd, bool applyNMS, + std::shared_ptr callInvoker); + + [[nodiscard("Registered non-void function")]] std::vector + generateFromString(std::string imageSource, double confidenceThreshold, + double iouThreshold, int32_t maxInstances, + std::vector classIndices, + bool returnMaskAtOriginalResolution, + std::string methodName); + + [[nodiscard("Registered non-void function")]] std::vector + generateFromFrame(jsi::Runtime &runtime, const jsi::Value &frameData, + double confidenceThreshold, double iouThreshold, + int32_t maxInstances, std::vector classIndices, + bool returnMaskAtOriginalResolution, + std::string methodName); + + [[nodiscard("Registered non-void function")]] std::vector + generateFromPixels(JSTensorViewIn tensorView, double confidenceThreshold, + double iouThreshold, int32_t maxInstances, + std::vector classIndices, + bool returnMaskAtOriginalResolution, + std::string methodName); + +protected: + cv::Size modelInputSize() const override; + +private: + std::vector runInference( + const cv::Mat &image, double confidenceThreshold, double iouThreshold, + int32_t maxInstances, const std::vector &classIndices, + bool returnMaskAtOriginalResolution, const std::string &methodName); + + TensorPtr buildInputTensor(const cv::Mat &image); + + std::vector + collectInstances(const std::vector &tensors, cv::Size originalSize, + cv::Size modelInputSize, double confidenceThreshold, + const std::vector &classIndices, + bool returnMaskAtOriginalResolution); + + void validateThresholds(double confidenceThreshold, + double iouThreshold) const; + void validateOutputTensors(const std::vector &tensors) const; + + std::set + prepareAllowedClasses(const std::vector &classIndices) const; + + // Model loading and input helpers + void ensureMethodLoaded(const std::string &methodName); + + std::tuple + extractDetectionData(const float *bboxData, const float *scoresData, + int32_t index); + + cv::Rect computeMaskCropRect(const utils::computer_vision::BBox &bboxModel, + cv::Size modelInputSize, cv::Size maskSize); + + cv::Rect addPaddingToRect(const cv::Rect &rect, cv::Size maskSize); + + cv::Mat + warpToOriginalResolution(const cv::Mat &probMat, const cv::Rect &maskRect, + cv::Size originalSize, cv::Size maskSize, + const utils::computer_vision::BBox &bboxOriginal); + + cv::Mat thresholdToBinary(const cv::Mat &probMat); + + std::vector + finalizeInstances(std::vector instances, double iouThreshold, + int32_t maxInstances) const; + + cv::Mat processMaskFromLogits( + const cv::Mat &logitsMat, const utils::computer_vision::BBox &bboxModel, + const utils::computer_vision::BBox &bboxOriginal, cv::Size modelInputSize, + cv::Size originalSize, bool warpToOriginal); + + std::optional normMean_; + std::optional normStd_; + bool applyNMS_; + std::string currentlyLoadedMethod_; +}; +} // namespace models::instance_segmentation + +REGISTER_CONSTRUCTOR(models::instance_segmentation::BaseInstanceSegmentation, + std::string, std::vector, std::vector, bool, + std::shared_ptr); +} // namespace rnexecutorch diff --git a/packages/react-native-executorch/common/rnexecutorch/models/instance_segmentation/Types.h b/packages/react-native-executorch/common/rnexecutorch/models/instance_segmentation/Types.h new file mode 100644 index 0000000000..2453cbef65 --- /dev/null +++ b/packages/react-native-executorch/common/rnexecutorch/models/instance_segmentation/Types.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include +#include + +namespace rnexecutorch::models::instance_segmentation::types { + +/** + * Represents a single detected instance in instance segmentation output. + * + * Contains bounding box coordinates, binary segmentation mask, class label, + * and confidence score. + */ +struct Instance { + utils::computer_vision::BBox bbox; + std::shared_ptr mask; + int32_t maskWidth; + int32_t maskHeight; + int32_t classIndex; + float score; + + Instance() = default; + Instance(utils::computer_vision::BBox bbox, + std::shared_ptr mask, int32_t maskWidth, + int32_t maskHeight, int32_t classIndex, float score) + : bbox(bbox), mask(std::move(mask)), maskWidth(maskWidth), + maskHeight(maskHeight), classIndex(classIndex), score(score) {} +}; + +} // namespace rnexecutorch::models::instance_segmentation::types diff --git a/packages/react-native-executorch/common/rnexecutorch/models/object_detection/ObjectDetection.cpp b/packages/react-native-executorch/common/rnexecutorch/models/object_detection/ObjectDetection.cpp index d30fc8aeed..8f31623d40 100644 --- a/packages/react-native-executorch/common/rnexecutorch/models/object_detection/ObjectDetection.cpp +++ b/packages/react-native-executorch/common/rnexecutorch/models/object_detection/ObjectDetection.cpp @@ -1,10 +1,13 @@ #include "ObjectDetection.h" +#include "Constants.h" #include #include #include #include #include +#include +#include namespace rnexecutorch::models::object_detection { @@ -83,10 +86,13 @@ ObjectDetection::postprocess(const std::vector &tensors, " exceeds labelNames size " + std::to_string(labelNames_.size()) + ". Ensure the labelMap covers all model output classes."); } - detections.emplace_back(x1, y1, x2, y2, labelNames_[labelIdx], scores[i]); + detections.emplace_back(utils::computer_vision::BBox{x1, y1, x2, y2}, + labelNames_[labelIdx], + static_cast(labelIdx), scores[i]); } - return utils::nonMaxSuppression(detections); + return utils::computer_vision::nonMaxSuppression(detections, + constants::IOU_THRESHOLD); } std::vector diff --git a/packages/react-native-executorch/common/rnexecutorch/models/object_detection/ObjectDetection.h b/packages/react-native-executorch/common/rnexecutorch/models/object_detection/ObjectDetection.h index d94087688a..1a7e72a6db 100644 --- a/packages/react-native-executorch/common/rnexecutorch/models/object_detection/ObjectDetection.h +++ b/packages/react-native-executorch/common/rnexecutorch/models/object_detection/ObjectDetection.h @@ -8,7 +8,6 @@ #include "Types.h" #include "rnexecutorch/metaprogramming/ConstructorHelpers.h" #include -#include namespace rnexecutorch { namespace models::object_detection { diff --git a/packages/react-native-executorch/common/rnexecutorch/models/object_detection/Types.h b/packages/react-native-executorch/common/rnexecutorch/models/object_detection/Types.h index 075ae91e0f..5e51ec075f 100644 --- a/packages/react-native-executorch/common/rnexecutorch/models/object_detection/Types.h +++ b/packages/react-native-executorch/common/rnexecutorch/models/object_detection/Types.h @@ -1,15 +1,21 @@ #pragma once +#include +#include #include namespace rnexecutorch::models::object_detection::types { struct Detection { - float x1; - float y1; - float x2; - float y2; + utils::computer_vision::BBox bbox; std::string label; + int32_t classIndex; float score; + + Detection() = default; + Detection(utils::computer_vision::BBox bbox, std::string label, + int32_t classIndex, float score) + : bbox(bbox), label(std::move(label)), classIndex(classIndex), + score(score) {} }; -} // namespace rnexecutorch::models::object_detection::types \ No newline at end of file +} // namespace rnexecutorch::models::object_detection::types diff --git a/packages/react-native-executorch/common/rnexecutorch/models/object_detection/Utils.cpp b/packages/react-native-executorch/common/rnexecutorch/models/object_detection/Utils.cpp deleted file mode 100644 index 198fe1fe50..0000000000 --- a/packages/react-native-executorch/common/rnexecutorch/models/object_detection/Utils.cpp +++ /dev/null @@ -1,64 +0,0 @@ -#include "Utils.h" -#include "Constants.h" - -namespace rnexecutorch::models::object_detection::utils { -float intersectionOverUnion(const types::Detection &a, - const types::Detection &b) { - float x1 = std::max(a.x1, b.x1); - float y1 = std::max(a.y1, b.y1); - float x2 = std::min(a.x2, b.x2); - float y2 = std::min(a.y2, b.y2); - - float intersectionArea = std::max(0.0f, x2 - x1) * std::max(0.0f, y2 - y1); - float areaA = (a.x2 - a.x1) * (a.y2 - a.y1); - float areaB = (b.x2 - b.x1) * (b.y2 - b.y1); - float unionArea = areaA + areaB - intersectionArea; - - return intersectionArea / unionArea; -} - -std::vector -nonMaxSuppression(std::vector detections) { - if (detections.empty()) { - return {}; - } - - // Sort by label, then by score - std::sort(detections.begin(), detections.end(), - [](const types::Detection &a, const types::Detection &b) { - if (a.label == b.label) { - return a.score > b.score; - } - return a.label < b.label; - }); - - std::vector result; - // Apply NMS for each label - for (size_t i = 0; i < detections.size();) { - std::string currentLabel = detections[i].label; - - std::vector labelDetections; - while (i < detections.size() && detections[i].label == currentLabel) { - labelDetections.push_back(detections[i]); - ++i; - } - - std::vector filteredLabelDetections; - while (!labelDetections.empty()) { - types::Detection current = labelDetections.front(); - filteredLabelDetections.push_back(current); - labelDetections.erase( - std::remove_if(labelDetections.begin(), labelDetections.end(), - [¤t](const types::Detection &other) { - return intersectionOverUnion(current, other) > - constants::IOU_THRESHOLD; - }), - labelDetections.end()); - } - result.insert(result.end(), filteredLabelDetections.begin(), - filteredLabelDetections.end()); - } - return result; -} - -} // namespace rnexecutorch::models::object_detection::utils \ No newline at end of file diff --git a/packages/react-native-executorch/common/rnexecutorch/models/object_detection/Utils.h b/packages/react-native-executorch/common/rnexecutorch/models/object_detection/Utils.h deleted file mode 100644 index 02e1018914..0000000000 --- a/packages/react-native-executorch/common/rnexecutorch/models/object_detection/Utils.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#include "Types.h" -#include - -namespace rnexecutorch::models::object_detection::utils { -float intersectionOverUnion(const types::Detection &a, - const types::Detection &b); -std::vector -nonMaxSuppression(std::vector detections); -} // namespace rnexecutorch::models::object_detection::utils \ No newline at end of file diff --git a/packages/react-native-executorch/common/rnexecutorch/tests/CMakeLists.txt b/packages/react-native-executorch/common/rnexecutorch/tests/CMakeLists.txt index bd359dcb8f..84fcc3200d 100644 --- a/packages/react-native-executorch/common/rnexecutorch/tests/CMakeLists.txt +++ b/packages/react-native-executorch/common/rnexecutorch/tests/CMakeLists.txt @@ -189,10 +189,10 @@ add_rn_test(ClassificationTests integration/ClassificationTest.cpp add_rn_test(ObjectDetectionTests integration/ObjectDetectionTest.cpp SOURCES ${RNEXECUTORCH_DIR}/models/object_detection/ObjectDetection.cpp - ${RNEXECUTORCH_DIR}/models/object_detection/Utils.cpp ${RNEXECUTORCH_DIR}/models/VisionModel.cpp ${RNEXECUTORCH_DIR}/utils/FrameProcessor.cpp ${RNEXECUTORCH_DIR}/utils/FrameExtractor.cpp + ${RNEXECUTORCH_DIR}/utils/computer_vision/Processing.cpp ${IMAGE_UTILS_SOURCES} LIBS opencv_deps android ) @@ -289,6 +289,17 @@ add_rn_test(TextToImageTests integration/TextToImageTest.cpp LIBS tokenizers_deps ) +add_rn_test(InstanceSegmentationTests integration/InstanceSegmentationTest.cpp + SOURCES + ${RNEXECUTORCH_DIR}/models/instance_segmentation/BaseInstanceSegmentation.cpp + ${RNEXECUTORCH_DIR}/models/VisionModel.cpp + ${RNEXECUTORCH_DIR}/utils/FrameProcessor.cpp + ${RNEXECUTORCH_DIR}/utils/FrameExtractor.cpp + ${RNEXECUTORCH_DIR}/utils/computer_vision/Processing.cpp + ${IMAGE_UTILS_SOURCES} + LIBS opencv_deps android +) + add_rn_test(OCRTests integration/OCRTest.cpp SOURCES ${RNEXECUTORCH_DIR}/models/ocr/OCR.cpp diff --git a/packages/react-native-executorch/common/rnexecutorch/tests/integration/InstanceSegmentationTest.cpp b/packages/react-native-executorch/common/rnexecutorch/tests/integration/InstanceSegmentationTest.cpp new file mode 100644 index 0000000000..cd5262fc0f --- /dev/null +++ b/packages/react-native-executorch/common/rnexecutorch/tests/integration/InstanceSegmentationTest.cpp @@ -0,0 +1,338 @@ +#include "BaseModelTests.h" +#include "VisionModelTests.h" +#include +#include +#include +#include +#include +#include + +using namespace rnexecutorch; +using namespace rnexecutorch::models::instance_segmentation; +using namespace model_tests; + +constexpr auto kValidInstanceSegModelPath = "yolo26n-seg.pte"; +constexpr auto kValidTestImagePath = + "file:///data/local/tmp/rnexecutorch_tests/segmentation_image.jpg"; +constexpr auto kMethodName = "forward_384"; + +// ============================================================================ +// Common tests via typed test suite +// ============================================================================ +namespace model_tests { +template <> struct ModelTraits { + using ModelType = BaseInstanceSegmentation; + + static ModelType createValid() { + return ModelType(kValidInstanceSegModelPath, {}, {}, true, nullptr); + } + + static ModelType createInvalid() { + return ModelType("nonexistent.pte", {}, {}, true, nullptr); + } + + static void callGenerate(ModelType &model) { + (void)model.generateFromString(kValidTestImagePath, 0.5, 0.5, 100, {}, true, + kMethodName); + } +}; +} // namespace model_tests + +using InstanceSegmentationTypes = ::testing::Types; +INSTANTIATE_TYPED_TEST_SUITE_P(InstanceSegmentation, CommonModelTest, + InstanceSegmentationTypes); +INSTANTIATE_TYPED_TEST_SUITE_P(InstanceSegmentation, VisionModelTest, + InstanceSegmentationTypes); + +// ============================================================================ +// Model-specific tests +// ============================================================================ +TEST(InstanceSegGenerateTests, InvalidImagePathThrows) { + BaseInstanceSegmentation model(kValidInstanceSegModelPath, {}, {}, true, + nullptr); + EXPECT_THROW((void)model.generateFromString("nonexistent_image.jpg", 0.5, 0.5, + 100, {}, true, kMethodName), + RnExecutorchError); +} + +TEST(InstanceSegGenerateTests, EmptyImagePathThrows) { + BaseInstanceSegmentation model(kValidInstanceSegModelPath, {}, {}, true, + nullptr); + EXPECT_THROW( + (void)model.generateFromString("", 0.5, 0.5, 100, {}, true, kMethodName), + RnExecutorchError); +} + +TEST(InstanceSegGenerateTests, EmptyMethodNameThrows) { + BaseInstanceSegmentation model(kValidInstanceSegModelPath, {}, {}, true, + nullptr); + EXPECT_THROW((void)model.generateFromString(kValidTestImagePath, 0.5, 0.5, + 100, {}, true, ""), + RnExecutorchError); +} + +TEST(InstanceSegGenerateTests, NegativeConfidenceThrows) { + BaseInstanceSegmentation model(kValidInstanceSegModelPath, {}, {}, true, + nullptr); + EXPECT_THROW((void)model.generateFromString(kValidTestImagePath, -0.1, 0.5, + 100, {}, true, kMethodName), + RnExecutorchError); +} + +TEST(InstanceSegGenerateTests, ConfidenceAboveOneThrows) { + BaseInstanceSegmentation model(kValidInstanceSegModelPath, {}, {}, true, + nullptr); + EXPECT_THROW((void)model.generateFromString(kValidTestImagePath, 1.1, 0.5, + 100, {}, true, kMethodName), + RnExecutorchError); +} + +TEST(InstanceSegGenerateTests, NegativeIouThresholdThrows) { + BaseInstanceSegmentation model(kValidInstanceSegModelPath, {}, {}, true, + nullptr); + EXPECT_THROW((void)model.generateFromString(kValidTestImagePath, 0.5, -0.1, + 100, {}, true, kMethodName), + RnExecutorchError); +} + +TEST(InstanceSegGenerateTests, IouThresholdAboveOneThrows) { + BaseInstanceSegmentation model(kValidInstanceSegModelPath, {}, {}, true, + nullptr); + EXPECT_THROW((void)model.generateFromString(kValidTestImagePath, 0.5, 1.1, + 100, {}, true, kMethodName), + RnExecutorchError); +} + +TEST(InstanceSegGenerateTests, ValidImageReturnsResults) { + BaseInstanceSegmentation model(kValidInstanceSegModelPath, {}, {}, true, + nullptr); + auto results = model.generateFromString(kValidTestImagePath, 0.3, 0.5, 100, + {}, true, kMethodName); + EXPECT_FALSE(results.empty()); +} + +TEST(InstanceSegGenerateTests, HighThresholdReturnsFewerResults) { + BaseInstanceSegmentation model(kValidInstanceSegModelPath, {}, {}, true, + nullptr); + auto lowResults = model.generateFromString(kValidTestImagePath, 0.1, 0.5, 100, + {}, true, kMethodName); + auto highResults = model.generateFromString(kValidTestImagePath, 0.9, 0.5, + 100, {}, true, kMethodName); + EXPECT_GE(lowResults.size(), highResults.size()); +} + +TEST(InstanceSegGenerateTests, MaxInstancesLimitsResults) { + BaseInstanceSegmentation model(kValidInstanceSegModelPath, {}, {}, true, + nullptr); + auto results = model.generateFromString(kValidTestImagePath, 0.1, 0.5, 2, {}, + true, kMethodName); + EXPECT_LE(results.size(), 2u); +} + +// ============================================================================ +// Result validation tests +// ============================================================================ +TEST(InstanceSegResultTests, InstancesHaveValidBoundingBoxes) { + BaseInstanceSegmentation model(kValidInstanceSegModelPath, {}, {}, true, + nullptr); + auto results = model.generateFromString(kValidTestImagePath, 0.3, 0.5, 100, + {}, true, kMethodName); + + for (const auto &inst : results) { + EXPECT_LE(inst.bbox.x1, inst.bbox.x2); + EXPECT_LE(inst.bbox.y1, inst.bbox.y2); + EXPECT_GE(inst.bbox.x1, 0.0f); + EXPECT_GE(inst.bbox.y1, 0.0f); + } +} + +TEST(InstanceSegResultTests, InstancesHaveValidScores) { + BaseInstanceSegmentation model(kValidInstanceSegModelPath, {}, {}, true, + nullptr); + auto results = model.generateFromString(kValidTestImagePath, 0.3, 0.5, 100, + {}, true, kMethodName); + + for (const auto &inst : results) { + EXPECT_GE(inst.score, 0.0f); + EXPECT_LE(inst.score, 1.0f); + } +} + +TEST(InstanceSegResultTests, InstancesHaveValidMasks) { + BaseInstanceSegmentation model(kValidInstanceSegModelPath, {}, {}, true, + nullptr); + auto results = model.generateFromString(kValidTestImagePath, 0.3, 0.5, 100, + {}, true, kMethodName); + + for (const auto &inst : results) { + EXPECT_GT(inst.maskWidth, 0); + EXPECT_GT(inst.maskHeight, 0); + EXPECT_EQ(inst.mask->size(), + static_cast(inst.maskWidth) * inst.maskHeight); + + for (size_t i = 0; i < inst.mask->size(); ++i) { + uint8_t val = inst.mask->data()[i]; + EXPECT_TRUE(val == 0 || val == 1); + } + } +} + +TEST(InstanceSegResultTests, InstancesHaveValidClassIndices) { + BaseInstanceSegmentation model(kValidInstanceSegModelPath, {}, {}, true, + nullptr); + auto results = model.generateFromString(kValidTestImagePath, 0.3, 0.5, 100, + {}, true, kMethodName); + + for (const auto &inst : results) { + EXPECT_GE(inst.classIndex, 0); + EXPECT_LT(inst.classIndex, 80); // COCO YOLO has 80 classes + } +} + +// ============================================================================ +// Class filtering tests +// ============================================================================ +TEST(InstanceSegFilterTests, ClassFilterReturnsOnlyMatchingClasses) { + BaseInstanceSegmentation model(kValidInstanceSegModelPath, {}, {}, true, + nullptr); + // Filter to class index 0 (PERSON in CocoLabelYolo) + std::vector classIndices = {0}; + auto results = model.generateFromString(kValidTestImagePath, 0.3, 0.5, 100, + classIndices, true, kMethodName); + + for (const auto &inst : results) { + EXPECT_EQ(inst.classIndex, 0); + } +} + +TEST(InstanceSegFilterTests, EmptyFilterReturnsAllClasses) { + BaseInstanceSegmentation model(kValidInstanceSegModelPath, {}, {}, true, + nullptr); + auto allResults = model.generateFromString(kValidTestImagePath, 0.3, 0.5, 100, + {}, true, kMethodName); + EXPECT_FALSE(allResults.empty()); + + auto noResults = model.generateFromString(kValidTestImagePath, 0.3, 0.5, 100, + {50}, true, kMethodName); + EXPECT_TRUE(noResults.empty()); +} + +// ============================================================================ +// Mask resolution tests +// ============================================================================ +TEST(InstanceSegMaskTests, LowResMaskIsSmallerThanOriginal) { + BaseInstanceSegmentation model(kValidInstanceSegModelPath, {}, {}, true, + nullptr); + auto hiRes = model.generateFromString(kValidTestImagePath, 0.3, 0.5, 100, {}, + true, kMethodName); + auto loRes = model.generateFromString(kValidTestImagePath, 0.3, 0.5, 100, {}, + false, kMethodName); + + if (!hiRes.empty() && !loRes.empty()) { + EXPECT_LE(loRes[0].mask->size(), hiRes[0].mask->size()); + } +} + +// ============================================================================ +// NMS tests +// ============================================================================ +TEST(InstanceSegNMSTests, NMSEnabledReturnsFewerOrEqualResults) { + BaseInstanceSegmentation modelWithNMS(kValidInstanceSegModelPath, {}, {}, + true, nullptr); + BaseInstanceSegmentation modelWithoutNMS(kValidInstanceSegModelPath, {}, {}, + false, nullptr); + + auto nmsResults = modelWithNMS.generateFromString( + kValidTestImagePath, 0.3, 0.5, 100, {}, true, kMethodName); + auto noNmsResults = modelWithoutNMS.generateFromString( + kValidTestImagePath, 0.3, 0.5, 100, {}, true, kMethodName); + + EXPECT_LE(nmsResults.size(), noNmsResults.size()); +} + +// ============================================================================ +// generateFromPixels tests +// ============================================================================ +TEST(InstanceSegPixelTests, ValidPixelDataReturnsResults) { + BaseInstanceSegmentation model(kValidInstanceSegModelPath, {}, {}, true, + nullptr); + constexpr int32_t width = 4, height = 4, channels = 3; + std::vector pixelData(width * height * channels, 128); + JSTensorViewIn tensorView{pixelData.data(), + {height, width, channels}, + executorch::aten::ScalarType::Byte}; + auto results = model.generateFromPixels(tensorView, 0.3, 0.5, 100, {}, true, + kMethodName); + EXPECT_GE(results.size(), 0u); +} + +TEST(InstanceSegPixelTests, NegativeConfidenceThrows) { + BaseInstanceSegmentation model(kValidInstanceSegModelPath, {}, {}, true, + nullptr); + constexpr int32_t width = 4, height = 4, channels = 3; + std::vector pixelData(width * height * channels, 128); + JSTensorViewIn tensorView{pixelData.data(), + {height, width, channels}, + executorch::aten::ScalarType::Byte}; + EXPECT_THROW((void)model.generateFromPixels(tensorView, -0.1, 0.5, 100, {}, + true, kMethodName), + RnExecutorchError); +} + +TEST(InstanceSegPixelTests, ConfidenceAboveOneThrows) { + BaseInstanceSegmentation model(kValidInstanceSegModelPath, {}, {}, true, + nullptr); + constexpr int32_t width = 4, height = 4, channels = 3; + std::vector pixelData(width * height * channels, 128); + JSTensorViewIn tensorView{pixelData.data(), + {height, width, channels}, + executorch::aten::ScalarType::Byte}; + EXPECT_THROW((void)model.generateFromPixels(tensorView, 1.1, 0.5, 100, {}, + true, kMethodName), + RnExecutorchError); +} + +// ============================================================================ +// Inherited method tests +// ============================================================================ +TEST(InstanceSegInheritedTests, GetInputShapeWorks) { + BaseInstanceSegmentation model(kValidInstanceSegModelPath, {}, {}, true, + nullptr); + auto shape = model.getInputShape(kMethodName, 0); + EXPECT_EQ(shape.size(), 4); + EXPECT_EQ(shape[0], 1); + EXPECT_EQ(shape[1], 3); +} + +TEST(InstanceSegInheritedTests, GetAllInputShapesWorks) { + BaseInstanceSegmentation model(kValidInstanceSegModelPath, {}, {}, true, + nullptr); + auto shapes = model.getAllInputShapes(kMethodName); + EXPECT_FALSE(shapes.empty()); +} + +TEST(InstanceSegInheritedTests, GetMethodMetaWorks) { + BaseInstanceSegmentation model(kValidInstanceSegModelPath, {}, {}, true, + nullptr); + auto result = model.getMethodMeta(kMethodName); + EXPECT_TRUE(result.ok()); +} + +// ============================================================================ +// Normalisation tests +// ============================================================================ +TEST(InstanceSegNormTests, ValidNormParamsDoesntThrow) { + const std::vector mean = {0.485f, 0.456f, 0.406f}; + const std::vector std = {0.229f, 0.224f, 0.225f}; + EXPECT_NO_THROW(BaseInstanceSegmentation(kValidInstanceSegModelPath, mean, + std, true, nullptr)); +} + +TEST(InstanceSegNormTests, ValidNormParamsGenerateSucceeds) { + const std::vector mean = {0.485f, 0.456f, 0.406f}; + const std::vector std = {0.229f, 0.224f, 0.225f}; + BaseInstanceSegmentation model(kValidInstanceSegModelPath, mean, std, true, + nullptr); + EXPECT_NO_THROW((void)model.generateFromString(kValidTestImagePath, 0.5, 0.5, + 100, {}, true, kMethodName)); +} diff --git a/packages/react-native-executorch/common/rnexecutorch/tests/integration/ObjectDetectionTest.cpp b/packages/react-native-executorch/common/rnexecutorch/tests/integration/ObjectDetectionTest.cpp index 6222f6d682..de36b3c545 100644 --- a/packages/react-native-executorch/common/rnexecutorch/tests/integration/ObjectDetectionTest.cpp +++ b/packages/react-native-executorch/common/rnexecutorch/tests/integration/ObjectDetectionTest.cpp @@ -120,10 +120,10 @@ TEST(ObjectDetectionGenerateTests, DetectionsHaveValidBoundingBoxes) { auto results = model.generateFromString(kValidTestImagePath, 0.3); for (const auto &detection : results) { - EXPECT_LE(detection.x1, detection.x2); - EXPECT_LE(detection.y1, detection.y2); - EXPECT_GE(detection.x1, 0.0f); - EXPECT_GE(detection.y1, 0.0f); + EXPECT_LE(detection.bbox.x1, detection.bbox.x2); + EXPECT_LE(detection.bbox.y1, detection.bbox.y2); + EXPECT_GE(detection.bbox.x1, 0.0f); + EXPECT_GE(detection.bbox.y1, 0.0f); } } diff --git a/packages/react-native-executorch/common/rnexecutorch/tests/run_tests.sh b/packages/react-native-executorch/common/rnexecutorch/tests/run_tests.sh index d12f8fbada..4f67bf0663 100755 --- a/packages/react-native-executorch/common/rnexecutorch/tests/run_tests.sh +++ b/packages/react-native-executorch/common/rnexecutorch/tests/run_tests.sh @@ -31,8 +31,8 @@ TEST_EXECUTABLES=( "SpeechToTextTests" "TextToSpeechTests" "LLMTests" - "ImageSegmentationTests" "TextToImageTests" + "InstanceSegmentationTests" "OCRTests" "VerticalOCRTests" ) @@ -77,6 +77,8 @@ MODELS=( "lfm2_5_vl_quantized_xnnpack_v2.pte|https://huggingface.co/software-mansion/react-native-executorch-lfm2.5-VL-1.6B/resolve/main/quantized/lfm2_5_vl_1_6b_8da4w_xnnpack.pte" "lfm2_vl_tokenizer.json|https://huggingface.co/software-mansion/react-native-executorch-lfm2.5-VL-1.6B/resolve/main/tokenizer.json" "lfm2_vl_tokenizer_config.json|https://huggingface.co/software-mansion/react-native-executorch-lfm2.5-VL-1.6B/resolve/main/tokenizer_config.json" + "yolo26n-seg.pte|https://huggingface.co/software-mansion/react-native-executorch-yolo26-seg/resolve/v0.8.0/yolo26n-seg/xnnpack/yolo26n-seg.pte" + "segmentation_image.jpg|https://upload.wikimedia.org/wikipedia/commons/thumb/8/85/Collage_audi.jpg/1280px-Collage_audi.jpg" ) # ============================================================================ diff --git a/packages/react-native-executorch/common/rnexecutorch/utils/computer_vision/Processing.cpp b/packages/react-native-executorch/common/rnexecutorch/utils/computer_vision/Processing.cpp new file mode 100644 index 0000000000..4343fba51b --- /dev/null +++ b/packages/react-native-executorch/common/rnexecutorch/utils/computer_vision/Processing.cpp @@ -0,0 +1,39 @@ +#include "Processing.h" +#include +#include + +namespace rnexecutorch::utils::computer_vision { + +float computeIoU(const BBox &a, const BBox &b) { + float x1 = std::max(a.x1, b.x1); + float y1 = std::max(a.y1, b.y1); + float x2 = std::min(a.x2, b.x2); + float y2 = std::min(a.y2, b.y2); + + float intersectionArea = std::max(0.0f, x2 - x1) * std::max(0.0f, y2 - y1); + float areaA = a.area(); + float areaB = b.area(); + float unionArea = areaA + areaB - intersectionArea; + + return (unionArea > 0.0f) ? (intersectionArea / unionArea) : 0.0f; +} + +BBox scaleBBox(const BBox &box, float widthRatio, float heightRatio) { + return { + .x1 = box.x1 * widthRatio, + .y1 = box.y1 * heightRatio, + .x2 = box.x2 * widthRatio, + .y2 = box.y2 * heightRatio, + }; +} + +BBox clipBBox(const BBox &box, float maxWidth, float maxHeight) { + return { + .x1 = std::max(0.0f, box.x1), + .y1 = std::max(0.0f, box.y1), + .x2 = std::min(maxWidth, box.x2), + .y2 = std::min(maxHeight, box.y2), + }; +} + +} // namespace rnexecutorch::utils::computer_vision diff --git a/packages/react-native-executorch/common/rnexecutorch/utils/computer_vision/Processing.h b/packages/react-native-executorch/common/rnexecutorch/utils/computer_vision/Processing.h new file mode 100644 index 0000000000..a3115fed82 --- /dev/null +++ b/packages/react-native-executorch/common/rnexecutorch/utils/computer_vision/Processing.h @@ -0,0 +1,55 @@ +#pragma once + +#include "Types.h" +#include +#include + +namespace rnexecutorch::utils::computer_vision { + +float computeIoU(const BBox &a, const BBox &b); + +BBox scaleBBox(const BBox &box, float widthRatio, float heightRatio); + +BBox clipBBox(const BBox &box, float maxWidth, float maxHeight); + +template +std::vector nonMaxSuppression(std::vector items, double iouThreshold) { + if (items.empty()) { + return {}; + } + + std::ranges::sort(items, + [](const T &a, const T &b) { return a.score > b.score; }); + + std::vector result; + std::vector suppressed(items.size(), false); + + for (size_t i = 0; i < items.size(); ++i) { + if (suppressed[i]) { + continue; + } + + result.push_back(items[i]); + + for (size_t j = i + 1; j < items.size(); ++j) { + if (suppressed[j]) { + continue; + } + + if constexpr (requires(T t) { t.classIndex; }) { + if (items[i].classIndex != items[j].classIndex) { + continue; + } + } + + float iou = computeIoU(items[i].bbox, items[j].bbox); + if (iou > iouThreshold) { + suppressed[j] = true; + } + } + } + + return result; +} + +} // namespace rnexecutorch::utils::computer_vision diff --git a/packages/react-native-executorch/common/rnexecutorch/utils/computer_vision/Types.h b/packages/react-native-executorch/common/rnexecutorch/utils/computer_vision/Types.h new file mode 100644 index 0000000000..6f3283b722 --- /dev/null +++ b/packages/react-native-executorch/common/rnexecutorch/utils/computer_vision/Types.h @@ -0,0 +1,32 @@ +#pragma once + +#include + +namespace rnexecutorch::utils::computer_vision { + +struct BBox { + float x1, y1, x2, y2; + + float width() const { return x2 - x1; } + + float height() const { return y2 - y1; } + + float area() const { return width() * height(); } + + bool isValid() const { + return x2 > x1 && y2 > y1 && x1 >= 0.0f && y1 >= 0.0f; + } + + BBox scale(float widthRatio, float heightRatio) const { + return {x1 * widthRatio, y1 * heightRatio, x2 * widthRatio, + y2 * heightRatio}; + } +}; + +template +concept HasBBoxAndScore = requires(T t) { + { t.bbox } -> std::convertible_to; + { t.score } -> std::convertible_to; +}; + +} // namespace rnexecutorch::utils::computer_vision diff --git a/packages/react-native-executorch/src/constants/commonVision.ts b/packages/react-native-executorch/src/constants/commonVision.ts index ba89c9f847..ffe59372ef 100644 --- a/packages/react-native-executorch/src/constants/commonVision.ts +++ b/packages/react-native-executorch/src/constants/commonVision.ts @@ -4,8 +4,13 @@ export const IMAGENET1K_MEAN: Triple = [0.485, 0.456, 0.406]; export const IMAGENET1K_STD: Triple = [0.229, 0.224, 0.225]; /** - * COCO dataset class labels used for object detection. + * COCO dataset class labels used by **RF-DETR** and **SSDLite** object detection models. * + * This enum is **1-indexed** and contains **91 classes**, matching the original COCO + * dataset category IDs. For **YOLO** models (object detection or instance segmentation), + * use {@link CocoLabelYolo} instead — a 0-indexed, 80-class variant. + * + * @see {@link CocoLabelYolo} for the YOLO-specific variant * @category Types */ export enum CocoLabel { @@ -100,3 +105,100 @@ export enum CocoLabel { TOOTHBRUSH = 90, HAIR_BRUSH = 91, } + +/** + * COCO dataset class labels used by **YOLO** models for instance segmentation and object detection. + * + * This enum is **0-indexed** (values start at 0) and contains exactly **80 classes** — + * the standard COCO detection subset without gaps. This differs from {@link CocoLabel}, + * which is 1-indexed with 91 classes and includes extra categories not present in the + * YOLO label set. + * + * Use this enum when working with YOLO models (e.g. `yolo26n-seg`). + * For RF-DETR or SSDLite models, use {@link CocoLabel}. + * + * @see {@link CocoLabel} for the RF-DETR / SSDLite variant + * @category Types + */ +export enum CocoLabelYolo { + PERSON = 0, + BICYCLE = 1, + CAR = 2, + MOTORCYCLE = 3, + AIRPLANE = 4, + BUS = 5, + TRAIN = 6, + TRUCK = 7, + BOAT = 8, + TRAFFIC_LIGHT = 9, + FIRE_HYDRANT = 10, + STOP_SIGN = 11, + PARKING_METER = 12, + BENCH = 13, + BIRD = 14, + CAT = 15, + DOG = 16, + HORSE = 17, + SHEEP = 18, + COW = 19, + ELEPHANT = 20, + BEAR = 21, + ZEBRA = 22, + GIRAFFE = 23, + BACKPACK = 24, + UMBRELLA = 25, + HANDBAG = 26, + TIE = 27, + SUITCASE = 28, + FRISBEE = 29, + SKIS = 30, + SNOWBOARD = 31, + SPORTS_BALL = 32, + KITE = 33, + BASEBALL_BAT = 34, + BASEBALL_GLOVE = 35, + SKATEBOARD = 36, + SURFBOARD = 37, + TENNIS_RACKET = 38, + BOTTLE = 39, + WINE_GLASS = 40, + CUP = 41, + FORK = 42, + KNIFE = 43, + SPOON = 44, + BOWL = 45, + BANANA = 46, + APPLE = 47, + SANDWICH = 48, + ORANGE = 49, + BROCCOLI = 50, + CARROT = 51, + HOT_DOG = 52, + PIZZA = 53, + DONUT = 54, + CAKE = 55, + CHAIR = 56, + COUCH = 57, + POTTED_PLANT = 58, + BED = 59, + DINING_TABLE = 60, + TOILET = 61, + TV = 62, + LAPTOP = 63, + MOUSE = 64, + REMOTE = 65, + KEYBOARD = 66, + CELL_PHONE = 67, + MICROWAVE = 68, + OVEN = 69, + TOASTER = 70, + SINK = 71, + REFRIGERATOR = 72, + BOOK = 73, + CLOCK = 74, + VASE = 75, + SCISSORS = 76, + TEDDY_BEAR = 77, + HAIR_DRIER = 78, + TOOTHBRUSH = 79, +} diff --git a/packages/react-native-executorch/src/constants/modelUrls.ts b/packages/react-native-executorch/src/constants/modelUrls.ts index 472b48ef60..069544990f 100644 --- a/packages/react-native-executorch/src/constants/modelUrls.ts +++ b/packages/react-native-executorch/src/constants/modelUrls.ts @@ -806,6 +806,63 @@ export const SELFIE_SEGMENTATION = { modelSource: SELFIE_SEGMENTATION_MODEL, } as const; +/** + * @category Models - Instance Segmentation + */ +const YOLO26N_SEG_MODEL = `${URL_PREFIX}-yolo26-seg/${NEXT_VERSION_TAG}/yolo26n-seg/xnnpack/yolo26n-seg.pte`; +const YOLO26S_SEG_MODEL = `${URL_PREFIX}-yolo26-seg/${NEXT_VERSION_TAG}/yolo26s-seg/xnnpack/yolo26s-seg.pte`; +const YOLO26M_SEG_MODEL = `${URL_PREFIX}-yolo26-seg/${NEXT_VERSION_TAG}/yolo26m-seg/xnnpack/yolo26m-seg.pte`; +const YOLO26L_SEG_MODEL = `${URL_PREFIX}-yolo26-seg/${NEXT_VERSION_TAG}/yolo26l-seg/xnnpack/yolo26l-seg.pte`; +const YOLO26X_SEG_MODEL = `${URL_PREFIX}-yolo26-seg/${NEXT_VERSION_TAG}/yolo26x-seg/xnnpack/yolo26x-seg.pte`; +const RF_DETR_NANO_SEG_MODEL = `${URL_PREFIX}-rfdetr-nano-segmentation/${NEXT_VERSION_TAG}/rfdetr_segmentation.pte`; +/** + * @category Models - Instance Segmentation + */ +export const YOLO26N_SEG = { + modelName: 'yolo26n-seg', + modelSource: YOLO26N_SEG_MODEL, +} as const; + +/** + * @category Models - Instance Segmentation + */ +export const YOLO26S_SEG = { + modelName: 'yolo26s-seg', + modelSource: YOLO26S_SEG_MODEL, +} as const; + +/** + * @category Models - Instance Segmentation + */ +export const YOLO26M_SEG = { + modelName: 'yolo26m-seg', + modelSource: YOLO26M_SEG_MODEL, +} as const; + +/** + * @category Models - Instance Segmentation + */ +export const YOLO26L_SEG = { + modelName: 'yolo26l-seg', + modelSource: YOLO26L_SEG_MODEL, +} as const; + +/** + * @category Models - Instance Segmentation + */ +export const YOLO26X_SEG = { + modelName: 'yolo26x-seg', + modelSource: YOLO26X_SEG_MODEL, +} as const; + +/** + * @category Models - Instance Segmentation + */ +export const RF_DETR_NANO_SEG = { + modelName: 'rfdetr-nano-seg', + modelSource: RF_DETR_NANO_SEG_MODEL, +} as const; + // Image Embeddings const CLIP_VIT_BASE_PATCH32_IMAGE_MODEL = `${URL_PREFIX}-clip-vit-base-patch32/${NEXT_VERSION_TAG}/xnnpack/clip_vit_base_patch32_vision_xnnpack_fp32.pte`; const CLIP_VIT_BASE_PATCH32_IMAGE_QUANTIZED_MODEL = `${URL_PREFIX}-clip-vit-base-patch32/${NEXT_VERSION_TAG}/xnnpack/clip_vit_base_patch32_vision_xnnpack_int8.pte`; diff --git a/packages/react-native-executorch/src/hooks/computer_vision/useInstanceSegmentation.ts b/packages/react-native-executorch/src/hooks/computer_vision/useInstanceSegmentation.ts new file mode 100644 index 0000000000..a66d43c0d3 --- /dev/null +++ b/packages/react-native-executorch/src/hooks/computer_vision/useInstanceSegmentation.ts @@ -0,0 +1,81 @@ +import { + InstanceSegmentationModule, + InstanceSegmentationLabels, +} from '../../modules/computer_vision/InstanceSegmentationModule'; +import { + InstanceSegmentationProps, + InstanceSegmentationType, + InstanceSegmentationModelSources, +} from '../../types/instanceSegmentation'; +import { useModuleFactory } from '../useModuleFactory'; + +/** + * React hook for managing an Instance Segmentation model instance. + * + * @typeParam C - A {@link InstanceSegmentationModelSources} config specifying which model to load. + * @param props - Configuration object containing `model` config and optional `preventLoad` flag. + * @returns An object with model state (`error`, `isReady`, `isGenerating`, `downloadProgress`) and a typed `forward` function. + * + * @example + * ```ts + * const { isReady, isGenerating, forward, error, downloadProgress } = + * useInstanceSegmentation({ + * model: { + * modelName: 'yolo26n-seg', + * modelSource: 'https://huggingface.co/.../yolo26n-seg.pte', + * }, + * }); + * + * if (!isReady) { + * return Loading: {(downloadProgress * 100).toFixed(0)}%; + * } + * + * const results = await forward('path/to/image.jpg', { + * confidenceThreshold: 0.5, + * inputSize: 640, + * }); + * ``` + * + * @category Hooks + */ +export const useInstanceSegmentation = < + C extends InstanceSegmentationModelSources, +>({ + model, + preventLoad = false, +}: InstanceSegmentationProps): InstanceSegmentationType< + InstanceSegmentationLabels +> => { + const { + error, + isReady, + isGenerating, + downloadProgress, + runForward, + instance, + runOnFrame, + } = useModuleFactory({ + factory: (config, onProgress) => + InstanceSegmentationModule.fromModelName(config, onProgress), + config: model, + deps: [model.modelName, model.modelSource], + preventLoad, + }); + + const forward: InstanceSegmentationType< + InstanceSegmentationLabels + >['forward'] = (imageSource, options) => + runForward((inst) => inst.forward(imageSource, options)); + + const getAvailableInputSizes = () => instance?.getAvailableInputSizes(); + + return { + error, + isReady, + isGenerating, + downloadProgress, + forward, + getAvailableInputSizes, + runOnFrame, + }; +}; diff --git a/packages/react-native-executorch/src/hooks/computer_vision/useSemanticSegmentation.ts b/packages/react-native-executorch/src/hooks/computer_vision/useSemanticSegmentation.ts index ae6ebed938..5c1e00ed0e 100644 --- a/packages/react-native-executorch/src/hooks/computer_vision/useSemanticSegmentation.ts +++ b/packages/react-native-executorch/src/hooks/computer_vision/useSemanticSegmentation.ts @@ -21,7 +21,7 @@ import { useModuleFactory } from '../useModuleFactory'; * @example * ```ts * const { isReady, forward } = useSemanticSegmentation({ - * model: { modelName: 'deeplab-v3', modelSource: DEEPLAB_V3_RESNET50 }, + * model: { modelName: 'deeplab-v3-resnet50', modelSource: DEEPLAB_V3_RESNET50 }, * }); * ``` * diff --git a/packages/react-native-executorch/src/index.ts b/packages/react-native-executorch/src/index.ts index 5bb4d3d134..fd91af5376 100644 --- a/packages/react-native-executorch/src/index.ts +++ b/packages/react-native-executorch/src/index.ts @@ -44,6 +44,12 @@ declare global { allClasses: string[] ) => Promise; var loadClassification: (source: string) => Promise; + var loadInstanceSegmentation: ( + source: string, + normMean: Triple | [], + normStd: Triple | [], + applyNMS: boolean + ) => any; var loadObjectDetection: ( source: string, normMean: Triple | [], @@ -99,9 +105,11 @@ declare global { ) => Promise; } // eslint-disable no-var + if ( global.loadStyleTransfer == null || global.loadSemanticSegmentation == null || + global.loadInstanceSegmentation == null || global.loadTextToImage == null || global.loadExecutorchModule == null || global.loadClassification == null || @@ -129,6 +137,7 @@ export * from './hooks/computer_vision/useClassification'; export * from './hooks/computer_vision/useObjectDetection'; export * from './hooks/computer_vision/useStyleTransfer'; export * from './hooks/computer_vision/useSemanticSegmentation'; +export * from './hooks/computer_vision/useInstanceSegmentation'; export * from './hooks/computer_vision/useOCR'; export * from './hooks/computer_vision/useVerticalOCR'; export * from './hooks/computer_vision/useImageEmbeddings'; @@ -148,6 +157,7 @@ export * from './modules/computer_vision/ClassificationModule'; export * from './modules/computer_vision/ObjectDetectionModule'; export * from './modules/computer_vision/StyleTransferModule'; export * from './modules/computer_vision/SemanticSegmentationModule'; +export * from './modules/computer_vision/InstanceSegmentationModule'; export * from './modules/computer_vision/OCRModule'; export * from './modules/computer_vision/VerticalOCRModule'; export * from './modules/computer_vision/ImageEmbeddingsModule'; @@ -173,6 +183,7 @@ export * from './utils/llms/context_strategy'; export * from './types/objectDetection'; export * from './types/ocr'; export * from './types/semanticSegmentation'; +export * from './types/instanceSegmentation'; export * from './types/llm'; export * from './types/vad'; export * from './types/common'; diff --git a/packages/react-native-executorch/src/modules/computer_vision/InstanceSegmentationModule.ts b/packages/react-native-executorch/src/modules/computer_vision/InstanceSegmentationModule.ts new file mode 100644 index 0000000000..cea3ccc3c7 --- /dev/null +++ b/packages/react-native-executorch/src/modules/computer_vision/InstanceSegmentationModule.ts @@ -0,0 +1,460 @@ +import { ResourceSource, LabelEnum, PixelData } from '../../types/common'; +import { + InstanceSegmentationModelSources, + InstanceSegmentationConfig, + InstanceSegmentationModelName, + InstanceModelNameOf, + NativeSegmentedInstance, + SegmentedInstance, + InstanceSegmentationOptions, +} from '../../types/instanceSegmentation'; +import { RnExecutorchErrorCode } from '../../errors/ErrorCodes'; +import { RnExecutorchError } from '../../errors/errorUtils'; +import { + fetchModelPath, + ResolveLabels as ResolveLabelsFor, + VisionLabeledModule, +} from './VisionLabeledModule'; +import { + CocoLabel, + CocoLabelYolo, + IMAGENET1K_MEAN, + IMAGENET1K_STD, +} from '../../constants/commonVision'; + +const YOLO_SEG_CONFIG = { + preprocessorConfig: undefined, + labelMap: CocoLabelYolo, + availableInputSizes: [384, 512, 640] as const, + defaultInputSize: 384, + defaultConfidenceThreshold: 0.5, + defaultIouThreshold: 0.5, + postprocessorConfig: { + applyNMS: false, + }, +} satisfies InstanceSegmentationConfig; + +const RF_DETR_NANO_SEG_CONFIG = { + preprocessorConfig: { normMean: IMAGENET1K_MEAN, normStd: IMAGENET1K_STD }, + labelMap: CocoLabel, + availableInputSizes: undefined, + defaultInputSize: undefined, //RFDetr exposes only one method named forward + defaultConfidenceThreshold: 0.5, + defaultIouThreshold: 0.5, + postprocessorConfig: { + applyNMS: true, + }, +} satisfies InstanceSegmentationConfig; + +/** + * Builds a reverse map from 0-based model class index to label key name, and + * computes the minimum enum value (offset) so TS enum values can be converted + * to 0-based model indices. + */ +function buildClassIndexMap(labelMap: LabelEnum): { + indexToLabel: Map; + minValue: number; +} { + const entries: [string, number][] = []; + for (const [name, value] of Object.entries(labelMap)) { + if (typeof value === 'number') entries.push([name, value]); + } + const minValue = Math.min(...entries.map(([, v]) => v)); + const indexToLabel = new Map(); + for (const [name, value] of entries) { + indexToLabel.set(value - minValue, name); + } + return { indexToLabel, minValue }; +} + +const ModelConfigs = { + 'yolo26n-seg': YOLO_SEG_CONFIG, + 'yolo26s-seg': YOLO_SEG_CONFIG, + 'yolo26m-seg': YOLO_SEG_CONFIG, + 'yolo26l-seg': YOLO_SEG_CONFIG, + 'yolo26x-seg': YOLO_SEG_CONFIG, + 'rfdetr-nano-seg': RF_DETR_NANO_SEG_CONFIG, +} as const satisfies Record< + InstanceSegmentationModelName, + | InstanceSegmentationConfig + | InstanceSegmentationConfig +>; + +/** @internal */ +type ModelConfigsType = typeof ModelConfigs; + +/** + * Resolves the label map type for a given built-in model name. + * + * @typeParam M - A built-in model name from {@link InstanceSegmentationModelName}. + * + * @category Types + */ +export type InstanceSegmentationLabels< + M extends InstanceSegmentationModelName, +> = ResolveLabels; + +/** + * @internal + * Resolves the label type: if `T` is a {@link InstanceSegmentationModelName}, looks up its labels + * from the built-in config; otherwise uses `T` directly as a {@link LabelEnum}. + */ +type ResolveLabels = + ResolveLabelsFor; + +/** + * Generic instance segmentation module with type-safe label maps. + * Use a model name (e.g. `'yolo26n-seg'`) as the generic parameter for pre-configured models, + * or a custom label enum for custom configs. + * + * Supported models (download from HuggingFace): + * - `yolo26n-seg`, `yolo26s-seg`, `yolo26m-seg`, `yolo26l-seg`, `yolo26x-seg` - YOLO models with COCO labels (80 classes) + * - `rfdetr-nano-seg` - RF-DETR Nano model with COCO labels (80 classes) + * + * @typeParam T - Either a pre-configured model name from {@link InstanceSegmentationModelName} + * or a custom label map conforming to {@link LabelEnum}. + * + * @category Typescript API + * + * @example + * ```ts + * const segmentation = await InstanceSegmentationModule.fromModelName({ + * modelName: 'yolo26n-seg', + * modelSource: 'https://huggingface.co/.../yolo26n-seg.pte', + * }); + * + * const results = await segmentation.forward('path/to/image.jpg', { + * confidenceThreshold: 0.5, + * iouThreshold: 0.45, + * maxInstances: 20, + * inputSize: 640, + * }); + * ``` + */ +export class InstanceSegmentationModule< + T extends InstanceSegmentationModelName | LabelEnum, +> extends VisionLabeledModule< + SegmentedInstance>[], + ResolveLabels +> { + private modelConfig: InstanceSegmentationConfig; + private classIndexToLabel: Map; + private labelEnumOffset: number; + + private constructor( + labelMap: ResolveLabels, + modelConfig: InstanceSegmentationConfig, + nativeModule: unknown, + classIndexToLabel: Map, + labelEnumOffset: number + ) { + super(labelMap, nativeModule); + this.modelConfig = modelConfig; + this.classIndexToLabel = classIndexToLabel; + this.labelEnumOffset = labelEnumOffset; + } + + /** + * Creates an instance segmentation module for a pre-configured model. + * The config object is discriminated by `modelName` — each model can require different fields. + * + * @param config - A {@link InstanceSegmentationModelSources} object specifying which model to load and where to fetch it from. + * @param onDownloadProgress - Optional callback to monitor download progress, receiving a value between 0 and 1. + * @returns A Promise resolving to an `InstanceSegmentationModule` instance typed to the chosen model's label map. + * + * @example + * ```ts + * const segmentation = await InstanceSegmentationModule.fromModelName({ + * modelName: 'yolo26n-seg', + * modelSource: 'https://huggingface.co/.../yolo26n-seg.pte', + * }); + * ``` + */ + static async fromModelName( + config: C, + onDownloadProgress: (progress: number) => void = () => {} + ): Promise>> { + const { modelName, modelSource } = config; + const modelConfig = ModelConfigs[modelName as keyof typeof ModelConfigs]; + + const path = await fetchModelPath(modelSource, onDownloadProgress); + + if (typeof global.loadInstanceSegmentation !== 'function') { + throw new RnExecutorchError( + RnExecutorchErrorCode.ModuleNotLoaded, + `global.loadInstanceSegmentation is not available` + ); + } + + const { indexToLabel, minValue } = buildClassIndexMap(modelConfig.labelMap); + + const nativeModule = await global.loadInstanceSegmentation( + path, + modelConfig.preprocessorConfig?.normMean || [], + modelConfig.preprocessorConfig?.normStd || [], + modelConfig.postprocessorConfig?.applyNMS ?? true + ); + + return new InstanceSegmentationModule>( + modelConfig.labelMap as ResolveLabels>, + modelConfig, + nativeModule, + indexToLabel, + minValue + ); + } + + /** + * Creates an instance segmentation module with a user-provided label map and custom config. + * Use this when working with a custom-exported segmentation model that is not one of the pre-configured models. + * + * @param modelSource - A fetchable resource pointing to the model binary. + * @param config - A {@link InstanceSegmentationConfig} object with the label map and optional preprocessing parameters. + * @param onDownloadProgress - Optional callback to monitor download progress, receiving a value between 0 and 1. + * @returns A Promise resolving to an `InstanceSegmentationModule` instance typed to the provided label map. + * + * @example + * ```ts + * const MyLabels = { PERSON: 0, CAR: 1 } as const; + * const segmentation = await InstanceSegmentationModule.fromCustomConfig( + * 'https://huggingface.co/.../custom_model.pte', + * { + * labelMap: MyLabels, + * availableInputSizes: [640], + * defaultInputSize: 640, + * defaultConfidenceThreshold: 0.5, + * defaultIouThreshold: 0.45, + * postprocessorConfig: { applyNMS: true }, + * }, + * ); + * ``` + */ + static async fromCustomConfig( + modelSource: ResourceSource, + config: InstanceSegmentationConfig, + onDownloadProgress: (progress: number) => void = () => {} + ): Promise> { + const path = await fetchModelPath(modelSource, onDownloadProgress); + + if (typeof global.loadInstanceSegmentation !== 'function') { + throw new RnExecutorchError( + RnExecutorchErrorCode.ModuleNotLoaded, + `global.loadInstanceSegmentation is not available` + ); + } + + const { indexToLabel, minValue } = buildClassIndexMap(config.labelMap); + + const nativeModule = await global.loadInstanceSegmentation( + path, + config.preprocessorConfig?.normMean || [], + config.preprocessorConfig?.normStd || [], + config.postprocessorConfig?.applyNMS ?? true + ); + + return new InstanceSegmentationModule( + config.labelMap as ResolveLabels, + config, + nativeModule, + indexToLabel, + minValue + ); + } + + /** + * Returns the available input sizes for this model, or undefined if the model accepts any size. + * + * @returns An array of available input sizes, or undefined if not constrained. + * + * @example + * ```ts + * const sizes = segmentation.getAvailableInputSizes(); + * console.log(sizes); // [384, 512, 640] for YOLO models, or undefined for RF-DETR + * ``` + */ + getAvailableInputSizes(): readonly number[] | undefined { + return this.modelConfig.availableInputSizes; + } + + /** + * Override runOnFrame to add label mapping for VisionCamera integration. + * The parent's runOnFrame returns raw native results with class indices; + * this override maps them to label strings and provides an options-based API. + */ + override get runOnFrame(): + | (( + frame: any, + options?: InstanceSegmentationOptions> + ) => SegmentedInstance>[]) + | null { + const baseRunOnFrame = super.runOnFrame; + if (!baseRunOnFrame) return null; + + // Convert Map to plain object for worklet serialization + const labelLookup: Record = {}; + this.classIndexToLabel.forEach((label, index) => { + labelLookup[index] = label; + }); + const labelEnumOffset = this.labelEnumOffset; + const defaultConfidenceThreshold = + this.modelConfig.defaultConfidenceThreshold ?? 0.5; + const defaultIouThreshold = this.modelConfig.defaultIouThreshold ?? 0.5; + const defaultInputSize = this.modelConfig.defaultInputSize; + + return ( + frame: any, + options?: InstanceSegmentationOptions> + ): SegmentedInstance>[] => { + 'worklet'; + + const confidenceThreshold = + options?.confidenceThreshold ?? defaultConfidenceThreshold; + const iouThreshold = options?.iouThreshold ?? defaultIouThreshold; + const maxInstances = options?.maxInstances ?? 100; + const returnMaskAtOriginalResolution = + options?.returnMaskAtOriginalResolution ?? true; + const inputSize = options?.inputSize ?? defaultInputSize; + const methodName = + inputSize !== undefined ? `forward_${inputSize}` : 'forward'; + + const classIndices = options?.classesOfInterest + ? options.classesOfInterest.map((label) => { + const labelStr = String(label); + const enumValue = (labelLookup as any)[labelStr]; + return typeof enumValue === 'number' + ? enumValue - labelEnumOffset + : -1; + }) + : []; + + const nativeResults = baseRunOnFrame( + frame, + confidenceThreshold, + iouThreshold, + maxInstances, + classIndices, + returnMaskAtOriginalResolution, + methodName + ); + return nativeResults.map((inst: any) => ({ + bbox: inst.bbox, + mask: inst.mask, + maskWidth: inst.maskWidth, + maskHeight: inst.maskHeight, + label: (labelLookup[inst.classIndex - labelEnumOffset] ?? + String(inst.classIndex)) as keyof ResolveLabels, + score: inst.score, + })); + }; + } + + /** + * Executes the model's forward pass to perform instance segmentation on the provided image. + * + * Supports two input types: + * 1. **String path/URI**: File path, URL, or Base64-encoded string + * 2. **PixelData**: Raw pixel data from image libraries (e.g., NitroImage) + * + * @param input - Image source (string path or PixelData object) + * @param options - Optional configuration for the segmentation process. Includes `confidenceThreshold`, `iouThreshold`, `maxInstances`, `classesOfInterest`, `returnMaskAtOriginalResolution`, and `inputSize`. + * @returns A Promise resolving to an array of {@link SegmentedInstance} objects with `bbox`, `mask`, `maskWidth`, `maskHeight`, `label`, `score`. + * @throws {RnExecutorchError} If the model is not loaded or if an invalid `inputSize` is provided. + * + * @example + * ```ts + * const results = await segmentation.forward('path/to/image.jpg', { + * confidenceThreshold: 0.6, + * iouThreshold: 0.5, + * maxInstances: 10, + * inputSize: 640, + * classesOfInterest: ['PERSON', 'CAR'], + * returnMaskAtOriginalResolution: true, + * }); + * + * results.forEach((inst) => { + * console.log(`${inst.label}: ${(inst.score * 100).toFixed(1)}%`); + * }); + * ``` + */ + async forward( + input: string | PixelData, + options?: InstanceSegmentationOptions> + ): Promise>[]> { + if (this.nativeModule == null) { + throw new RnExecutorchError( + RnExecutorchErrorCode.ModuleNotLoaded, + 'The model is currently not loaded.' + ); + } + + const confidenceThreshold = + options?.confidenceThreshold ?? + this.modelConfig.defaultConfidenceThreshold ?? + 0.5; + const iouThreshold = + options?.iouThreshold ?? this.modelConfig.defaultIouThreshold ?? 0.5; + const maxInstances = options?.maxInstances ?? 100; + const returnMaskAtOriginalResolution = + options?.returnMaskAtOriginalResolution ?? true; + + const inputSize = options?.inputSize ?? this.modelConfig.defaultInputSize; + + if ( + this.modelConfig.availableInputSizes && + inputSize !== undefined && + !this.modelConfig.availableInputSizes.includes( + inputSize as (typeof this.modelConfig.availableInputSizes)[number] + ) + ) { + throw new RnExecutorchError( + RnExecutorchErrorCode.InvalidArgument, + `Invalid inputSize: ${inputSize}. Available sizes: ${this.modelConfig.availableInputSizes.join(', ')}` + ); + } + + const methodName = + inputSize !== undefined ? `forward_${inputSize}` : 'forward'; + + const classIndices = options?.classesOfInterest + ? options.classesOfInterest.map((label) => { + const labelStr = String(label); + const enumValue = this.labelMap[labelStr as keyof ResolveLabels]; + return typeof enumValue === 'number' + ? enumValue - this.labelEnumOffset + : -1; + }) + : []; + + const nativeResult: NativeSegmentedInstance[] = + typeof input === 'string' + ? await this.nativeModule.generateFromString( + input, + confidenceThreshold, + iouThreshold, + maxInstances, + classIndices, + returnMaskAtOriginalResolution, + methodName + ) + : await this.nativeModule.generateFromPixels( + input, + confidenceThreshold, + iouThreshold, + maxInstances, + classIndices, + returnMaskAtOriginalResolution, + methodName + ); + + return nativeResult.map((inst) => ({ + bbox: inst.bbox, + mask: inst.mask, + maskWidth: inst.maskWidth, + maskHeight: inst.maskHeight, + label: (this.classIndexToLabel.get( + inst.classIndex - this.labelEnumOffset + ) ?? String(inst.classIndex)) as keyof ResolveLabels, + score: inst.score, + })); + } +} diff --git a/packages/react-native-executorch/src/types/instanceSegmentation.ts b/packages/react-native-executorch/src/types/instanceSegmentation.ts new file mode 100644 index 0000000000..1256f85f3e --- /dev/null +++ b/packages/react-native-executorch/src/types/instanceSegmentation.ts @@ -0,0 +1,223 @@ +import { RnExecutorchError } from '../errors/errorUtils'; +import { LabelEnum, ResourceSource, Triple, Frame, PixelData } from './common'; +import { Bbox } from './objectDetection'; + +/** + * Raw instance returned from the native C++ side, carrying a numeric + * `classIndex` instead of a resolved label string. + * + * @internal + */ +export interface NativeSegmentedInstance { + bbox: Bbox; + mask: Uint8Array; + maskWidth: number; + maskHeight: number; + classIndex: number; + score: number; +} + +/** + * Represents a single detected instance in instance segmentation output. + * + * @typeParam L - The label map type for the model, must conform to {@link LabelEnum}. + * @category Types + * @property {Bbox} bbox - The bounding box of the instance. + * @property {Uint8Array} mask - Binary mask (0 or 1) representing the instance. + * @property {number} maskWidth - Width of the mask array. + * @property {number} maskHeight - Height of the mask array. + * @property {keyof L} label - The class label of the instance. + * @property {number} score - Confidence score [0, 1]. + */ +export interface SegmentedInstance { + bbox: Bbox; + mask: Uint8Array; + maskWidth: number; + maskHeight: number; + label: keyof L; + score: number; +} + +/** + * Options for instance segmentation forward pass. + * + * @typeParam L - The label map type for the model, must conform to {@link LabelEnum}. + * @category Types + */ +export interface InstanceSegmentationOptions { + /** + * Minimum confidence threshold for including instances. + * Defaults to model's defaultConfidenceThreshold (typically 0.5). + */ + confidenceThreshold?: number; + /** + * IoU threshold for non-maximum suppression. + * Defaults to model's defaultIouThreshold (typically 0.5). + */ + iouThreshold?: number; + /** + * Maximum number of instances to return. Default: 100 + */ + maxInstances?: number; + /** + * Filter to include only specific classes. + */ + classesOfInterest?: (keyof L)[]; + /** + * Whether to return masks at original image resolution. Default: true + */ + returnMaskAtOriginalResolution?: boolean; + /** + * Input size for the model (e.g., 384, 512, 640). + * Must be one of the model's availableInputSizes. + * Defaults to model's defaultInputSize. + */ + inputSize?: number; +} + +/** + * Configuration for an instance segmentation model. + * + * @typeParam T - The label map type for the model, must conform to {@link LabelEnum}. + * @category Types + * + * @remarks + * The `availableInputSizes` and `defaultInputSize` fields are mutually inclusive: + * - **Either both must be provided** (for models with multiple input sizes), or + * - **Both must be omitted** (for models with a single input size). + * + * This discriminated union ensures type safety and prevents partial configuration. + */ +export type InstanceSegmentationConfig = { + labelMap: T; + preprocessorConfig?: { + normMean?: Triple; + normStd?: Triple; + }; + postprocessorConfig?: { applyNMS?: boolean }; + defaultConfidenceThreshold?: number; + defaultIouThreshold?: number; +} & ( + | { + availableInputSizes: readonly number[]; + defaultInputSize: number; + } + | { + availableInputSizes?: undefined; + defaultInputSize?: undefined; + } +); + +/** + * Per-model config for {@link InstanceSegmentationModule.fromModelName}. + * Each model name maps to its required fields. + * + * @category Types + */ +export type InstanceSegmentationModelSources = + | { modelName: 'yolo26n-seg'; modelSource: ResourceSource } + | { modelName: 'yolo26s-seg'; modelSource: ResourceSource } + | { modelName: 'yolo26m-seg'; modelSource: ResourceSource } + | { modelName: 'yolo26l-seg'; modelSource: ResourceSource } + | { modelName: 'yolo26x-seg'; modelSource: ResourceSource } + | { modelName: 'rfdetr-nano-seg'; modelSource: ResourceSource }; + +/** + * Union of all built-in instance segmentation model names. + * + * @category Types + */ +export type InstanceSegmentationModelName = + InstanceSegmentationModelSources['modelName']; + +/** + * Extracts the instance segmentation model name from a {@link InstanceSegmentationModelSources} config object. + * + * @category Types + */ +export type InstanceModelNameOf = + C['modelName']; + +/** + * Props for the `useInstanceSegmentation` hook. + * + * @typeParam C - A {@link InstanceSegmentationModelSources} config specifying which built-in model to load. + * @property model - The model config containing `modelName` and `modelSource`. + * @property {boolean} [preventLoad] - Boolean that can prevent automatic model loading (and downloading the data if you load it for the first time) after running the hook. + * + * @category Types + */ +export interface InstanceSegmentationProps< + C extends InstanceSegmentationModelSources, +> { + model: C; + preventLoad?: boolean; +} + +/** + * Return type for the `useInstanceSegmentation` hook. + * Manages the state and operations for instance segmentation models. + * + * @typeParam L - The label map type for the model, must conform to {@link LabelEnum}. + * + * @category Types + */ +export interface InstanceSegmentationType { + /** + * Contains the error object if the model failed to load, download, or encountered a runtime error during segmentation. + */ + error: RnExecutorchError | null; + + /** + * Indicates whether the instance segmentation model is loaded and ready to process images. + */ + isReady: boolean; + + /** + * Indicates whether the model is currently processing an image. + */ + isGenerating: boolean; + + /** + * Represents the download progress of the model binary as a value between 0 and 1. + */ + downloadProgress: number; + + /** + * Executes the model's forward pass to perform instance segmentation on the provided image. + * @param imageSource - A string (e.g., a file path, URI ) or PixelData representing the image source to be processed. + * @param options - Optional configuration for the segmentation process. + * @returns A Promise resolving to an array of {@link SegmentedInstance} objects. + * @throws {RnExecutorchError} If the model is not loaded or is currently processing another image. + */ + forward: ( + imageSource: string | PixelData, + options?: InstanceSegmentationOptions + ) => Promise[]>; + + /** + * Returns the available input sizes for this model, or undefined if the model accepts single forward input size. + * @returns An array of available input sizes, or undefined if not constrained. + */ + getAvailableInputSizes: () => readonly number[] | undefined; + + /** + * Synchronous worklet function for real-time VisionCamera frame processing. + * Automatically handles native buffer extraction and cleanup. + * + * **Use this for VisionCamera frame processing in worklets.** + * For async processing, use `forward()` instead. + * + * Available after model is loaded (`isReady: true`). + * + * @param frame - VisionCamera Frame object + * @param options - Optional configuration for the segmentation process. + * @returns Array of SegmentedInstance objects representing detected items in the frame. + */ + runOnFrame: + | (( + frame: Frame, + options?: InstanceSegmentationOptions + ) => SegmentedInstance[]) + | null; +} diff --git a/skills/canary/react-native-executorch/references/reference-cv.md b/skills/canary/react-native-executorch/references/reference-cv.md index 171b14a417..abe4ee7f08 100644 --- a/skills/canary/react-native-executorch/references/reference-cv.md +++ b/skills/canary/react-native-executorch/references/reference-cv.md @@ -1,6 +1,6 @@ --- title: Computer Vision models usage -description: Reference for using Image Classification, Semantic Segmentation, and Object Detection models. +description: Reference for using Image Classification, Image Segmentation, and Object Detection models. --- # useClassification @@ -139,8 +139,8 @@ For the latest available models check out exported models in [this HuggingFace S ## Additional references -- [useSemanticSegmentation docs](https://docs.swmansion.com/react-native-executorch/docs/hooks/computer-vision/useSemanticSegmentation) -- useSemanticSegmentation API reference](https://docs.swmansion.com/react-native-executorch/docs/api-reference/functions/useSemanticSegmentation) +- [useSemanticSegmentation docs](https://docs.swmansion.com/react-native-executorch/docs/hooks/computer-vision/useImageSegmentation) +- useSemanticSegmentation API reference](https://docs.swmansion.com/react-native-executorch/docs/api-reference/functions/useImageSegmentation) - [HuggingFace Segmentation collection](https://huggingface.co/collections/software-mansion/image-segmentation) - [Typescript API implementation of segmentation](https://docs.swmansion.com/react-native-executorch/docs/typescript-api/computer-vision/SemanticSegmentationModule) diff --git a/skills/react-native-executorch/references/reference-cv.md b/skills/react-native-executorch/references/reference-cv.md index 52fcf3093c..3bb24acd09 100644 --- a/skills/react-native-executorch/references/reference-cv.md +++ b/skills/react-native-executorch/references/reference-cv.md @@ -1,6 +1,6 @@ --- title: Computer Vision models usage -description: Reference for using Image Classification, Semantic Segmentation, and Object Detection models. +description: Reference for using Image Classification, Image Segmentation, and Object Detection models. --- # useClassification @@ -139,8 +139,8 @@ For the latest available models check out exported models in [this HuggingFace S ## Additional references -- [useSemanticSegmentation docs](https://docs.swmansion.com/react-native-executorch/docs/hooks/computer-vision/useSemanticSegmentation) -- [useSemanticSegmentation API reference](https://docs.swmansion.com/react-native-executorch/docs/api-reference/functions/useSemanticSegmentation) +- [useSemanticSegmentation docs](https://docs.swmansion.com/react-native-executorch/docs/hooks/computer-vision/useImageSegmentation) +- [useSemanticSegmentation API reference](https://docs.swmansion.com/react-native-executorch/docs/api-reference/functions/useImageSegmentation) - [HuggingFace Segmentation collection](https://huggingface.co/collections/software-mansion/image-segmentation) - [Typescript API implementation of segmentation](https://docs.swmansion.com/react-native-executorch/docs/typescript-api/computer-vision/SemanticSegmentationModule) diff --git a/yarn.lock b/yarn.lock index 0ed00cdfd9..54c920700d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -162,9 +162,9 @@ __metadata: languageName: node linkType: hard -"@babel/helper-define-polyfill-provider@npm:^0.6.5, @babel/helper-define-polyfill-provider@npm:^0.6.6": - version: 0.6.6 - resolution: "@babel/helper-define-polyfill-provider@npm:0.6.6" +"@babel/helper-define-polyfill-provider@npm:^0.6.5, @babel/helper-define-polyfill-provider@npm:^0.6.7": + version: 0.6.7 + resolution: "@babel/helper-define-polyfill-provider@npm:0.6.7" dependencies: "@babel/helper-compilation-targets": "npm:^7.28.6" "@babel/helper-plugin-utils": "npm:^7.28.6" @@ -173,7 +173,7 @@ __metadata: resolve: "npm:^1.22.11" peerDependencies: "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - checksum: 10/1c725c47bafb10ae4527aff6741b44ca49b18bf7005ae4583b15f992783e7c1d7687eab1a5583a373b5494160d46e91e29145280bd850e97d36b8b01bc5fef99 + checksum: 10/a13fe848018aad9745018ab1f8c95520f3872572cc931c70c77acacf9b8f774b49e22ece6cc89143867935e2efac3fc7eb325f2846dedc1621d83f2f8f7d8ad1 languageName: node linkType: hard @@ -1881,9 +1881,9 @@ __metadata: linkType: hard "@cspell/dict-companies@npm:^3.1.15": - version: 3.2.10 - resolution: "@cspell/dict-companies@npm:3.2.10" - checksum: 10/e3e6be70cef9be8fa7da86ed2ce1150545062175f6335d30de9bcac2cf509fbfa82d5428333129be823698870df48f78abbff4c6ee50b465efb1951adaa4cdf3 + version: 3.2.11 + resolution: "@cspell/dict-companies@npm:3.2.11" + checksum: 10/7ebaa0a31c9d8c20645083b925bda258bbf36e8c139151c59883f426a84883adcca0ca990d9a8b343e8be8e8fbc3c841e8566c69759db9c074aa2e5917fa05d8 languageName: node linkType: hard @@ -1909,9 +1909,9 @@ __metadata: linkType: hard "@cspell/dict-css@npm:^4.0.17": - version: 4.0.19 - resolution: "@cspell/dict-css@npm:4.0.19" - checksum: 10/293edf2d26b33d85ea5828b499e77e5a6a471f7c8ac754adf7c574dac62363f10d46d1de47afc20b8904554212834b671bbd8e98669e59908856ca1c46c787db + version: 4.1.1 + resolution: "@cspell/dict-css@npm:4.1.1" + checksum: 10/83d67eecd3c25a7b057779e48bc1b04b05e831e403dd6e5ff15b9025bda4012553ac356abba548ba5aad820324a5b66738f3dc3dd05378ed273956bada4ef03e languageName: node linkType: hard @@ -1972,16 +1972,16 @@ __metadata: linkType: hard "@cspell/dict-en_us@npm:^4.4.3": - version: 4.4.29 - resolution: "@cspell/dict-en_us@npm:4.4.29" - checksum: 10/537f254cb34484f6b44a9c935ad8ad32924c950b338c23ee2494c26a65b8eb06cfa16452ef5bf3102322ec4be3a55563a582d8efd3f651b64c41b5ee16f90738 + version: 4.4.31 + resolution: "@cspell/dict-en_us@npm:4.4.31" + checksum: 10/22fd608c1d4a4d195766a387cc26e161786e9da729711eca2a6a1f4eb8a89b56cd65c3c9889db65755f18714d83c3b0aa3097b5e17240a31d51040afb58fec39 languageName: node linkType: hard "@cspell/dict-filetypes@npm:^3.0.11": - version: 3.0.16 - resolution: "@cspell/dict-filetypes@npm:3.0.16" - checksum: 10/30f05d175b96cd4bb6d9504af929255987aa922e29223eb3fc00c712a375c0843903935b8c648fe514011904386b7c652ac87a197aa8b65f47695a6e96d3e293 + version: 3.0.17 + resolution: "@cspell/dict-filetypes@npm:3.0.17" + checksum: 10/91bfbefb96dcc29b705af41669211e6ad4c5fa2dde984b21f590620c9536f9d22d36ddbcaaf82acb4eb16b6431c3799170058f3e4a2e5c34563b57645e6e546d languageName: node linkType: hard @@ -1993,9 +1993,9 @@ __metadata: linkType: hard "@cspell/dict-fonts@npm:^4.0.4": - version: 4.0.5 - resolution: "@cspell/dict-fonts@npm:4.0.5" - checksum: 10/6ad5c29a73d2b70ac341ea1bc5baaa3bd9b1b43d5c0d54bacb5e226fbc55072b778d2fb9d121acf66ab2d1c39e5d605af099369005004f9678f8b853e4b9d707 + version: 4.0.6 + resolution: "@cspell/dict-fonts@npm:4.0.6" + checksum: 10/29ad0cf68cd3e6cef29e4c257de6ad5034ea4cdfdaf5aafd8ff6fc7918da205252d9de68c3a82b36169d21aa4abd17a4f1f4e352821f4f90f527d252d8830e8a languageName: node linkType: hard @@ -2056,9 +2056,9 @@ __metadata: linkType: hard "@cspell/dict-html@npm:^4.0.11": - version: 4.0.14 - resolution: "@cspell/dict-html@npm:4.0.14" - checksum: 10/b6047177b6012d467926c27777391cc3f6ebc522c6f4dce01e58a8100dfac61d5cecefc59ba5994728d41c7480d7c9d994d0f8673197740526f6afbed544094a + version: 4.0.15 + resolution: "@cspell/dict-html@npm:4.0.15" + checksum: 10/e5bcee3ac7757ba0e937c8ea76cb0111ac32105e6ce42babdd305fb226f22125228f0df2eebad0692436bb40fe2f74d17a04bc3e9ef6c89370a65bdb6c17232a languageName: node linkType: hard @@ -2119,14 +2119,14 @@ __metadata: linkType: hard "@cspell/dict-markdown@npm:^2.0.10": - version: 2.0.14 - resolution: "@cspell/dict-markdown@npm:2.0.14" + version: 2.0.16 + resolution: "@cspell/dict-markdown@npm:2.0.16" peerDependencies: - "@cspell/dict-css": ^4.0.19 - "@cspell/dict-html": ^4.0.14 + "@cspell/dict-css": ^4.1.1 + "@cspell/dict-html": ^4.0.15 "@cspell/dict-html-symbol-entities": ^4.0.5 "@cspell/dict-typescript": ^3.2.3 - checksum: 10/7ca267a7089bcf63aebb94b2934220aa81e1b747427e7c7998fc6852818a726da8ccaf380e0b76314cfc57504308446361dc6f1daef3cd73f3d60479563c636a + checksum: 10/951986c4485bb83de9c7547dcba9e2aa8510b89d3146d636c2fac26b9a684230a317d2b6d1812f5fc8e274b77889680e40fb4631392f81da1e3466ee9baa3017 languageName: node linkType: hard @@ -2145,9 +2145,9 @@ __metadata: linkType: hard "@cspell/dict-npm@npm:^5.2.1": - version: 5.2.35 - resolution: "@cspell/dict-npm@npm:5.2.35" - checksum: 10/c98bddeb07203be6be2d41dff724779c35459e1e1a1f9c552dd5224b8862a21cb7c13fd32236ba62ad20df0f99b05f8d409a2330c047aaae2ba94dbdf8312984 + version: 5.2.37 + resolution: "@cspell/dict-npm@npm:5.2.37" + checksum: 10/9686a603449e0a587f39fa075db13aa1e9d50c0c5d00e15fd96678a710edd6a2fa4107da81d874819071bb994b37975a1ac663e058b66e97c711e1ce6ef636d8 languageName: node linkType: hard @@ -2189,9 +2189,9 @@ __metadata: linkType: hard "@cspell/dict-ruby@npm:^5.0.8": - version: 5.1.0 - resolution: "@cspell/dict-ruby@npm:5.1.0" - checksum: 10/afe89daa54a6c3be62686f4f74fe924dabb4077e9aead71bf26e211885d068066df414857f27da258a130e2e06ecb5eac394225bc120607bebcb08746b6188f9 + version: 5.1.1 + resolution: "@cspell/dict-ruby@npm:5.1.1" + checksum: 10/8f09db1fdcf0e990ab56ac61b40cc2a7a3c2fbd01f8b4dc2de7018c31a1fad98228a67225f49801fbc2353624df8492b9775d8d04e37bfea2182cb390a686c83 languageName: node linkType: hard @@ -2217,9 +2217,9 @@ __metadata: linkType: hard "@cspell/dict-software-terms@npm:^5.0.5": - version: 5.1.23 - resolution: "@cspell/dict-software-terms@npm:5.1.23" - checksum: 10/69f9149df10071ae0901d71f01a6475f329cd2571973f6d356ebf210e0405a935ec28d5b07d58eff0a861a58e59406c96e7f608ed677ec1126ade90cf6d9f118 + version: 5.2.0 + resolution: "@cspell/dict-software-terms@npm:5.2.0" + checksum: 10/4886e01ee75458d3ed7dfe8d7eabbb440fc1d0d001ea76dbee06720598cb650573ea756455e3e6f83689464baf3d05d4882b8f01e2bde6d8846351b5d9d62d41 languageName: node linkType: hard @@ -2333,30 +2333,30 @@ __metadata: linkType: hard "@emnapi/core@npm:^1.4.3": - version: 1.8.1 - resolution: "@emnapi/core@npm:1.8.1" + version: 1.9.0 + resolution: "@emnapi/core@npm:1.9.0" dependencies: - "@emnapi/wasi-threads": "npm:1.1.0" + "@emnapi/wasi-threads": "npm:1.2.0" tslib: "npm:^2.4.0" - checksum: 10/904ea60c91fc7d8aeb4a8f2c433b8cfb47c50618f2b6f37429fc5093c857c6381c60628a5cfbc3a7b0d75b0a288f21d4ed2d4533e82f92c043801ef255fd6a5c + checksum: 10/52d8dc5ba0d6814c5061686b8286d84cc5349c8fc09de3a9c4175bc2369c2890b335f7b03e55bc19ce3033158962cd817522fcb3bdeb1feb9ba7a060d61b69ab languageName: node linkType: hard "@emnapi/runtime@npm:^1.4.3": - version: 1.8.1 - resolution: "@emnapi/runtime@npm:1.8.1" + version: 1.9.0 + resolution: "@emnapi/runtime@npm:1.9.0" dependencies: tslib: "npm:^2.4.0" - checksum: 10/26725e202d4baefdc4a6ba770f703dfc80825a27c27a08c22bac1e1ce6f8f75c47b4fe9424d9b63239463c33ef20b650f08d710da18dfa1164a95e5acb865dba + checksum: 10/d04a7e67676c2560d5394a01d63532af943760cf19cc8f375390a345aeab2b19e9ee35485b06b5c211df18f947fb14ac50658fca5c4067946f1e50af3490b3b5 languageName: node linkType: hard -"@emnapi/wasi-threads@npm:1.1.0": - version: 1.1.0 - resolution: "@emnapi/wasi-threads@npm:1.1.0" +"@emnapi/wasi-threads@npm:1.2.0": + version: 1.2.0 + resolution: "@emnapi/wasi-threads@npm:1.2.0" dependencies: tslib: "npm:^2.4.0" - checksum: 10/0d557e75262d2f4c95cb2a456ba0785ef61f919ce488c1d76e5e3acfd26e00c753ef928cd80068363e0c166ba8cc0141305daf0f81aad5afcd421f38f11e0f4e + checksum: 10/c8e48c7200530744dc58170d2e25933b61433e4a0c50b4f192f5d8d4b065c7023dbfc48dac0afadbc29bd239013f2ae454c6e54e0ca6e8248402bf95c9e77e22 languageName: node linkType: hard @@ -2403,11 +2403,11 @@ __metadata: linkType: hard "@evilmartians/lefthook@npm:^2.1.1": - version: 2.1.1 - resolution: "@evilmartians/lefthook@npm:2.1.1" + version: 2.1.4 + resolution: "@evilmartians/lefthook@npm:2.1.4" bin: lefthook: bin/index.js - checksum: 10/5bf44fa530f91d7856de3141b953d909ed7feebe7cb23f7fa24631c50580329892328e0bbd4173327e9061dbee419cfc18d58406ed2356dd883507523e373b5a + checksum: 10/098fcfa67f450c7447f646d3b053739dc6962da7b72af4efc624250d8934b83adffa25c80955760c42959d05b747ad2c72aa0fdb1c05a6a873ab8aa4aa63010b conditions: (os=darwin | os=linux | os=win32) & (cpu=x64 | cpu=arm64 | cpu=ia32) languageName: node linkType: hard @@ -2856,9 +2856,9 @@ __metadata: linkType: hard "@huggingface/jinja@npm:^0.5.0": - version: 0.5.5 - resolution: "@huggingface/jinja@npm:0.5.5" - checksum: 10/9575f8a689ab2c31f2540ea2dd82da2bcddb536196c3de3ae8f3b2fa06f01f47eb6a0513b410bbeac90fea7123733a15bf5d484f45fe3132bc16232a60f0dce3 + version: 0.5.6 + resolution: "@huggingface/jinja@npm:0.5.6" + checksum: 10/4d347fb22b9f866d31cdc29dc5e821a6848f29ade03bbc206896c6500e6ece2def51add010d75ea2cf8e0bb34882bd5ee9701916fd12ed852e424ee889b0b11e languageName: node linkType: hard @@ -2937,17 +2937,17 @@ __metadata: languageName: node linkType: hard -"@jest/console@npm:30.2.0": - version: 30.2.0 - resolution: "@jest/console@npm:30.2.0" +"@jest/console@npm:30.3.0": + version: 30.3.0 + resolution: "@jest/console@npm:30.3.0" dependencies: - "@jest/types": "npm:30.2.0" + "@jest/types": "npm:30.3.0" "@types/node": "npm:*" chalk: "npm:^4.1.2" - jest-message-util: "npm:30.2.0" - jest-util: "npm:30.2.0" + jest-message-util: "npm:30.3.0" + jest-util: "npm:30.3.0" slash: "npm:^3.0.0" - checksum: 10/7cda9793962afa5c7fcfdde0ff5012694683b17941ee3c6a55ea9fd9a02f1c51ec4b4c767b867e1226f85a26af1d0f0d72c6a344e34c5bc4300312ebffd6e50b + checksum: 10/aa23c9d77975b7c547190394272454e3563fbf0f99e7170f8b3f8128d83aaa62ad2d07291633e0ec1d4aee7e256dcf0b254bd391cdcd039d0ce6eac6ca835b24 languageName: node linkType: hard @@ -2965,44 +2965,43 @@ __metadata: languageName: node linkType: hard -"@jest/core@npm:30.2.0": - version: 30.2.0 - resolution: "@jest/core@npm:30.2.0" +"@jest/core@npm:30.3.0": + version: 30.3.0 + resolution: "@jest/core@npm:30.3.0" dependencies: - "@jest/console": "npm:30.2.0" + "@jest/console": "npm:30.3.0" "@jest/pattern": "npm:30.0.1" - "@jest/reporters": "npm:30.2.0" - "@jest/test-result": "npm:30.2.0" - "@jest/transform": "npm:30.2.0" - "@jest/types": "npm:30.2.0" + "@jest/reporters": "npm:30.3.0" + "@jest/test-result": "npm:30.3.0" + "@jest/transform": "npm:30.3.0" + "@jest/types": "npm:30.3.0" "@types/node": "npm:*" ansi-escapes: "npm:^4.3.2" chalk: "npm:^4.1.2" ci-info: "npm:^4.2.0" exit-x: "npm:^0.2.2" graceful-fs: "npm:^4.2.11" - jest-changed-files: "npm:30.2.0" - jest-config: "npm:30.2.0" - jest-haste-map: "npm:30.2.0" - jest-message-util: "npm:30.2.0" + jest-changed-files: "npm:30.3.0" + jest-config: "npm:30.3.0" + jest-haste-map: "npm:30.3.0" + jest-message-util: "npm:30.3.0" jest-regex-util: "npm:30.0.1" - jest-resolve: "npm:30.2.0" - jest-resolve-dependencies: "npm:30.2.0" - jest-runner: "npm:30.2.0" - jest-runtime: "npm:30.2.0" - jest-snapshot: "npm:30.2.0" - jest-util: "npm:30.2.0" - jest-validate: "npm:30.2.0" - jest-watcher: "npm:30.2.0" - micromatch: "npm:^4.0.8" - pretty-format: "npm:30.2.0" + jest-resolve: "npm:30.3.0" + jest-resolve-dependencies: "npm:30.3.0" + jest-runner: "npm:30.3.0" + jest-runtime: "npm:30.3.0" + jest-snapshot: "npm:30.3.0" + jest-util: "npm:30.3.0" + jest-validate: "npm:30.3.0" + jest-watcher: "npm:30.3.0" + pretty-format: "npm:30.3.0" slash: "npm:^3.0.0" peerDependencies: node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 peerDependenciesMeta: node-notifier: optional: true - checksum: 10/6763bb1efd937778f009821cd94c3705d3c31a156258a224b8745c1e0887976683f5413745ffb361b526f0fa2692e36aaa963aa197cc77ba932cff9d6d28af9d + checksum: 10/76f8561686e3bbaf2fcdc9c2391d47fef403e5fe0a936a48762ca60bcaf18692b5d2f8e5e26610cc43e965a6b120458dc9a7484e7e8ffb459118b61a90c2063d languageName: node linkType: hard @@ -3056,22 +3055,22 @@ __metadata: languageName: node linkType: hard -"@jest/diff-sequences@npm:30.0.1": - version: 30.0.1 - resolution: "@jest/diff-sequences@npm:30.0.1" - checksum: 10/0ddb7c7ba92d6057a2ee51a9cfc2155b77cca707fe959167466ea02dcb0687018cc3c22b9622f25f3a417d6ad370e2d4dcfedf9f1410dc9c02954a7484423cc7 +"@jest/diff-sequences@npm:30.3.0": + version: 30.3.0 + resolution: "@jest/diff-sequences@npm:30.3.0" + checksum: 10/0d5b6e1599c5e0bb702f0804e7f93bbe4911b5929c40fd6a77c06105711eae24d709c8964e8d623cc70c34b7dc7262d76a115a6eb05f1576336cdb6c46593e7c languageName: node linkType: hard -"@jest/environment@npm:30.2.0": - version: 30.2.0 - resolution: "@jest/environment@npm:30.2.0" +"@jest/environment@npm:30.3.0": + version: 30.3.0 + resolution: "@jest/environment@npm:30.3.0" dependencies: - "@jest/fake-timers": "npm:30.2.0" - "@jest/types": "npm:30.2.0" + "@jest/fake-timers": "npm:30.3.0" + "@jest/types": "npm:30.3.0" "@types/node": "npm:*" - jest-mock: "npm:30.2.0" - checksum: 10/e168a4ff328980eb9fde5e43aea80807fd0b2dbd4579ae8f68a03415a1e58adf5661db298054fa2351c7cb2b5a74bf67b8ab996656cf5927d0b0d0b6e2c2966b + jest-mock: "npm:30.3.0" + checksum: 10/9b64add2e5430411ca997aed23cd34786d0e87562f5930ad0d4160df51435ae061809fcaa6bbc6c0ff9f0ba5f1241a5ce9a32ec772fa1d7c6b022f0169b622a4 languageName: node linkType: hard @@ -3087,12 +3086,12 @@ __metadata: languageName: node linkType: hard -"@jest/expect-utils@npm:30.2.0": - version: 30.2.0 - resolution: "@jest/expect-utils@npm:30.2.0" +"@jest/expect-utils@npm:30.3.0": + version: 30.3.0 + resolution: "@jest/expect-utils@npm:30.3.0" dependencies: "@jest/get-type": "npm:30.1.0" - checksum: 10/f2442f1bceb3411240d0f16fd0074377211b4373d3b8b2dc28929e861b6527a6deb403a362c25afa511d933cda4dfbdc98d4a08eeb51ee4968f7cb0299562349 + checksum: 10/766fd24f527a13004c542c2642b68b9142270801ab20bd448a559d9c2f40af079d0eb9ec9520a47f97b4d6c7d0837ba46e86284f53c939f11d9fcbda73a11e19 languageName: node linkType: hard @@ -3105,13 +3104,13 @@ __metadata: languageName: node linkType: hard -"@jest/expect@npm:30.2.0": - version: 30.2.0 - resolution: "@jest/expect@npm:30.2.0" +"@jest/expect@npm:30.3.0": + version: 30.3.0 + resolution: "@jest/expect@npm:30.3.0" dependencies: - expect: "npm:30.2.0" - jest-snapshot: "npm:30.2.0" - checksum: 10/d950d95a64d5c6a39d56171dabb8dbe59423096231bb4f21d8ee0019878e6626701ac9d782803dc2589e2799ed39704031f818533f8a3e571b57032eafa85d12 + expect: "npm:30.3.0" + jest-snapshot: "npm:30.3.0" + checksum: 10/74832945a2b18c7b962b27e0ca4d25d19a29d1c3ca6fe4a9c23946025b4146799e62a81d50060ac7bcaf7036fb477aa350ddf300e215333b42d013a3d9f8ba2b languageName: node linkType: hard @@ -3125,17 +3124,17 @@ __metadata: languageName: node linkType: hard -"@jest/fake-timers@npm:30.2.0": - version: 30.2.0 - resolution: "@jest/fake-timers@npm:30.2.0" +"@jest/fake-timers@npm:30.3.0": + version: 30.3.0 + resolution: "@jest/fake-timers@npm:30.3.0" dependencies: - "@jest/types": "npm:30.2.0" - "@sinonjs/fake-timers": "npm:^13.0.0" + "@jest/types": "npm:30.3.0" + "@sinonjs/fake-timers": "npm:^15.0.0" "@types/node": "npm:*" - jest-message-util: "npm:30.2.0" - jest-mock: "npm:30.2.0" - jest-util: "npm:30.2.0" - checksum: 10/c2df66576ba8049b07d5f239777243e21fcdaa09a446be1e55fac709d6273e2a926c1562e0372c3013142557ed9d386381624023549267a667b6e1b656e37fe6 + jest-message-util: "npm:30.3.0" + jest-mock: "npm:30.3.0" + jest-util: "npm:30.3.0" + checksum: 10/e39d30b61ae85485bfa0b1d86d62d866d33964bf0b95b8b4f45d2f1f1baa94fd7e134c7729370a58cb67b58d2b860fb396290b5c271782ed4d3728341027549b languageName: node linkType: hard @@ -3160,15 +3159,15 @@ __metadata: languageName: node linkType: hard -"@jest/globals@npm:30.2.0": - version: 30.2.0 - resolution: "@jest/globals@npm:30.2.0" +"@jest/globals@npm:30.3.0": + version: 30.3.0 + resolution: "@jest/globals@npm:30.3.0" dependencies: - "@jest/environment": "npm:30.2.0" - "@jest/expect": "npm:30.2.0" - "@jest/types": "npm:30.2.0" - jest-mock: "npm:30.2.0" - checksum: 10/d4a331d3847cebb3acefe120350d8a6bb5517c1403de7cd2b4dc67be425f37ba0511beee77d6837b4da2d93a25a06d6f829ad7837da365fae45e1da57523525c + "@jest/environment": "npm:30.3.0" + "@jest/expect": "npm:30.3.0" + "@jest/types": "npm:30.3.0" + jest-mock: "npm:30.3.0" + checksum: 10/485bdc0f35faf3e76cb451b75e16892d87f7ab5757e290b1a9e849a3af0ef81c47abddb188fbc0442a4689514cf0551e34d13970c9cf03610a269c39f800ff46 languageName: node linkType: hard @@ -3194,30 +3193,30 @@ __metadata: languageName: node linkType: hard -"@jest/reporters@npm:30.2.0": - version: 30.2.0 - resolution: "@jest/reporters@npm:30.2.0" +"@jest/reporters@npm:30.3.0": + version: 30.3.0 + resolution: "@jest/reporters@npm:30.3.0" dependencies: "@bcoe/v8-coverage": "npm:^0.2.3" - "@jest/console": "npm:30.2.0" - "@jest/test-result": "npm:30.2.0" - "@jest/transform": "npm:30.2.0" - "@jest/types": "npm:30.2.0" + "@jest/console": "npm:30.3.0" + "@jest/test-result": "npm:30.3.0" + "@jest/transform": "npm:30.3.0" + "@jest/types": "npm:30.3.0" "@jridgewell/trace-mapping": "npm:^0.3.25" "@types/node": "npm:*" chalk: "npm:^4.1.2" collect-v8-coverage: "npm:^1.0.2" exit-x: "npm:^0.2.2" - glob: "npm:^10.3.10" + glob: "npm:^10.5.0" graceful-fs: "npm:^4.2.11" istanbul-lib-coverage: "npm:^3.0.0" istanbul-lib-instrument: "npm:^6.0.0" istanbul-lib-report: "npm:^3.0.0" istanbul-lib-source-maps: "npm:^5.0.0" istanbul-reports: "npm:^3.1.3" - jest-message-util: "npm:30.2.0" - jest-util: "npm:30.2.0" - jest-worker: "npm:30.2.0" + jest-message-util: "npm:30.3.0" + jest-util: "npm:30.3.0" + jest-worker: "npm:30.3.0" slash: "npm:^3.0.0" string-length: "npm:^4.0.2" v8-to-istanbul: "npm:^9.0.1" @@ -3226,7 +3225,7 @@ __metadata: peerDependenciesMeta: node-notifier: optional: true - checksum: 10/3848b59bf740c10c4e5c234dcc41c54adbd74932bf05d1d1582d09d86e9baa86ddaf3c43903505fd042ba1203c2889a732137d08058ce9dc0069ba33b5d5373d + checksum: 10/50cc20d9e908239352c5c6bc594c2880e30e16db6f8c0657513d1a46e3a761ed20464afa604af35bc72cbca0eac6cd34829c075513ecf725af03161a7662097e languageName: node linkType: hard @@ -3285,15 +3284,15 @@ __metadata: languageName: node linkType: hard -"@jest/snapshot-utils@npm:30.2.0": - version: 30.2.0 - resolution: "@jest/snapshot-utils@npm:30.2.0" +"@jest/snapshot-utils@npm:30.3.0": + version: 30.3.0 + resolution: "@jest/snapshot-utils@npm:30.3.0" dependencies: - "@jest/types": "npm:30.2.0" + "@jest/types": "npm:30.3.0" chalk: "npm:^4.1.2" graceful-fs: "npm:^4.2.11" natural-compare: "npm:^1.4.0" - checksum: 10/6b30ab2b0682117e3ce775e70b5be1eb01e1ea53a74f12ac7090cd1a5f37e9b795cd8de83853afa7b4b799c96b1c482499aa993ca2034ea0679525d32b7f9625 + checksum: 10/2214d4f0f33d2363a0785c0ba75066bf4ed4beefd5b2d2a5c3124d66ab92f91163f03696be625223bdb0527f1e6360c4b306ba9ae421aeb966d4a57d6d972099 languageName: node linkType: hard @@ -3319,15 +3318,15 @@ __metadata: languageName: node linkType: hard -"@jest/test-result@npm:30.2.0": - version: 30.2.0 - resolution: "@jest/test-result@npm:30.2.0" +"@jest/test-result@npm:30.3.0": + version: 30.3.0 + resolution: "@jest/test-result@npm:30.3.0" dependencies: - "@jest/console": "npm:30.2.0" - "@jest/types": "npm:30.2.0" + "@jest/console": "npm:30.3.0" + "@jest/types": "npm:30.3.0" "@types/istanbul-lib-coverage": "npm:^2.0.6" collect-v8-coverage: "npm:^1.0.2" - checksum: 10/f58f79c3c3ba6dd15325e05b0b5a300777cd8cc38327f622608b6fe849b1073ee9633e33d1e5d7ef5b97a1ce71543d0ad92674b7a279f53033143e8dd7c22959 + checksum: 10/89bed2adc8077e592deb74e4a9bd6c1d937c1ae18805b3b4e799d00276ab91a4974b7dc1f38dc12a5da7712ef0ba2e63c69245696e63f4a7b292fc79bb3981b7 languageName: node linkType: hard @@ -3343,15 +3342,15 @@ __metadata: languageName: node linkType: hard -"@jest/test-sequencer@npm:30.2.0": - version: 30.2.0 - resolution: "@jest/test-sequencer@npm:30.2.0" +"@jest/test-sequencer@npm:30.3.0": + version: 30.3.0 + resolution: "@jest/test-sequencer@npm:30.3.0" dependencies: - "@jest/test-result": "npm:30.2.0" + "@jest/test-result": "npm:30.3.0" graceful-fs: "npm:^4.2.11" - jest-haste-map: "npm:30.2.0" + jest-haste-map: "npm:30.3.0" slash: "npm:^3.0.0" - checksum: 10/7923964b27048b2233858b32aa1b34d4dd9e404311626d944a706bcdcaa0b1585f43f2ffa3fa893ecbf133566f31ba2b79ab5eaaaf674b8558c6c7029ecbea5e + checksum: 10/d2a593733b029bae5e1a60249fb8ced2fa701e2b336b69de4cd0a1e0008f4373ab1329422f819e209d1d95a29959bd0cc131c7f94c9ad8f3831833f79a08f997 languageName: node linkType: hard @@ -3367,26 +3366,25 @@ __metadata: languageName: node linkType: hard -"@jest/transform@npm:30.2.0": - version: 30.2.0 - resolution: "@jest/transform@npm:30.2.0" +"@jest/transform@npm:30.3.0": + version: 30.3.0 + resolution: "@jest/transform@npm:30.3.0" dependencies: "@babel/core": "npm:^7.27.4" - "@jest/types": "npm:30.2.0" + "@jest/types": "npm:30.3.0" "@jridgewell/trace-mapping": "npm:^0.3.25" babel-plugin-istanbul: "npm:^7.0.1" chalk: "npm:^4.1.2" convert-source-map: "npm:^2.0.0" fast-json-stable-stringify: "npm:^2.1.0" graceful-fs: "npm:^4.2.11" - jest-haste-map: "npm:30.2.0" + jest-haste-map: "npm:30.3.0" jest-regex-util: "npm:30.0.1" - jest-util: "npm:30.2.0" - micromatch: "npm:^4.0.8" + jest-util: "npm:30.3.0" pirates: "npm:^4.0.7" slash: "npm:^3.0.0" write-file-atomic: "npm:^5.0.1" - checksum: 10/c75d72d524c2a50ea6c05778a9b76a6e48bc228a3390896a6edd4416f7b4954ee0a07e229ed7b4949ce8889324b70034c784751e3fc455a25648bd8dcad17d0d + checksum: 10/279b6b73f59c274d7011febcbc0a1fa8939e8f677801a0a9bd95b9cf49244957267f3769c8cd541ae8026d8176089cd5e55f0f8d5361ec7788970978f4f394b4 languageName: node linkType: hard @@ -3413,9 +3411,9 @@ __metadata: languageName: node linkType: hard -"@jest/types@npm:30.2.0": - version: 30.2.0 - resolution: "@jest/types@npm:30.2.0" +"@jest/types@npm:30.3.0": + version: 30.3.0 + resolution: "@jest/types@npm:30.3.0" dependencies: "@jest/pattern": "npm:30.0.1" "@jest/schemas": "npm:30.0.5" @@ -3424,7 +3422,7 @@ __metadata: "@types/node": "npm:*" "@types/yargs": "npm:^17.0.33" chalk: "npm:^4.1.2" - checksum: 10/f50fcaea56f873a51d19254ab16762f2ea8ca88e3e08da2e496af5da2b67c322915a4fcd0153803cc05063ffe87ebef2ab4330e0a1b06ab984a26c916cbfc26b + checksum: 10/d6943cc270f07c7bc1ee6f3bb9ad1263ce7897d1a282221bf1d27499d77f2a68cfa6625ca73c193d3f81fe22a8e00635cd7acb5e73a546965c172219c81ec12c languageName: node linkType: hard @@ -3497,11 +3495,11 @@ __metadata: linkType: hard "@kesha-antonov/react-native-background-downloader@npm:^4.4.5": - version: 4.5.2 - resolution: "@kesha-antonov/react-native-background-downloader@npm:4.5.2" + version: 4.5.4 + resolution: "@kesha-antonov/react-native-background-downloader@npm:4.5.4" peerDependencies: react-native: ">=0.57.0" - checksum: 10/2543563caa62aa9362ef4c37dcb11f4b762cb9803869bbdf5a45260c2bbb3ae68336450920594f768ea29500dd6fe04958cbf07a90620617737df5862718343e + checksum: 10/fbc32bc4149e7682d0f158f74c6f59b4ecb840ea739f70ff2e17a32d36d610471a261b3b0bbe364f5ff4d91a7495b4cd5ca99d47faea9a8d32213712ddb0fa71 languageName: node linkType: hard @@ -4398,13 +4396,13 @@ __metadata: languageName: node linkType: hard -"@react-native/babel-plugin-codegen@npm:0.84.0": - version: 0.84.0 - resolution: "@react-native/babel-plugin-codegen@npm:0.84.0" +"@react-native/babel-plugin-codegen@npm:0.84.1": + version: 0.84.1 + resolution: "@react-native/babel-plugin-codegen@npm:0.84.1" dependencies: "@babel/traverse": "npm:^7.25.3" - "@react-native/codegen": "npm:0.84.0" - checksum: 10/ffe50339cf1413e352aedc9691ff1ef2724f0e83e9d65001e47669a855b74846bf37ce0005e5f3a46dbfcaa6b29410fb66000b353808e342133ee58860b165d0 + "@react-native/codegen": "npm:0.84.1" + checksum: 10/59f2571e2855755f3fbc99a05e9956a150d8f10343219d88641160bfae956e8d8703a9e9c6c3df0ee758bdb6672bf512aac0f04034b3ea4cf91769a86e076ccc languageName: node linkType: hard @@ -4518,9 +4516,9 @@ __metadata: languageName: node linkType: hard -"@react-native/babel-preset@npm:0.84.0": - version: 0.84.0 - resolution: "@react-native/babel-preset@npm:0.84.0" +"@react-native/babel-preset@npm:0.84.1": + version: 0.84.1 + resolution: "@react-native/babel-preset@npm:0.84.1" dependencies: "@babel/core": "npm:^7.25.2" "@babel/plugin-proposal-export-default-from": "npm:^7.24.7" @@ -4551,13 +4549,13 @@ __metadata: "@babel/plugin-transform-runtime": "npm:^7.24.7" "@babel/plugin-transform-typescript": "npm:^7.25.2" "@babel/plugin-transform-unicode-regex": "npm:^7.24.7" - "@react-native/babel-plugin-codegen": "npm:0.84.0" + "@react-native/babel-plugin-codegen": "npm:0.84.1" babel-plugin-syntax-hermes-parser: "npm:0.32.0" babel-plugin-transform-flow-enums: "npm:^0.0.2" react-refresh: "npm:^0.14.0" peerDependencies: "@babel/core": "*" - checksum: 10/b92783961506db98dfd6a77d1e3c9556b33cec0d6f208556dc01c1505b899b122067e64bb4a61ff36ed4b4019e5fcfe63781c4e714562a266a214ed0a4c6b301 + checksum: 10/9d362f7fa72a6b2abb9adb67cdc5dca1fe9f8a38bfd66cef01e5d99ef6b715df8842446d74859151829d883aad7ffcc46faa7cdf4b67aef9a787270a7600974c languageName: node linkType: hard @@ -4595,9 +4593,9 @@ __metadata: languageName: node linkType: hard -"@react-native/codegen@npm:0.84.0": - version: 0.84.0 - resolution: "@react-native/codegen@npm:0.84.0" +"@react-native/codegen@npm:0.84.1": + version: 0.84.1 + resolution: "@react-native/codegen@npm:0.84.1" dependencies: "@babel/core": "npm:^7.25.2" "@babel/parser": "npm:^7.25.3" @@ -4608,7 +4606,7 @@ __metadata: yargs: "npm:^17.6.2" peerDependencies: "@babel/core": "*" - checksum: 10/1b81de2bcccbf570efb42cf8aaa4a551ececdc510ce9c9c2230f8eafdd0eeec25729307340993a769eca94c99e343499ba0aeec4427e3e7976914c01055732f7 + checksum: 10/2fa65f813c56afd4deafce8adcfaa31c5b87fb5b4ee66b625dd1171c745c6114ac192b3dd7f028e71c5ccaae752a9096b12d70d980d99bd71806b043e9234078 languageName: node linkType: hard @@ -4742,10 +4740,10 @@ __metadata: languageName: node linkType: hard -"@react-native/js-polyfills@npm:0.84.0": - version: 0.84.0 - resolution: "@react-native/js-polyfills@npm:0.84.0" - checksum: 10/5b2927aee2f7106cfe2531f9fce4ef1563c207223a34a6ec2aec886b475b78e507a8dd23a9e2c9a0afafbaff56a53e23dec93c6b4dc14b224d026bbed4304d97 +"@react-native/js-polyfills@npm:0.84.1": + version: 0.84.1 + resolution: "@react-native/js-polyfills@npm:0.84.1" + checksum: 10/7074e66d54f302864b580d2bf2c048cdb4c37fc4f6fd9dee0faeb4084d524c2f1e7fa60014a54bdcaaa51d75955fbac6a9274e25b806783eee7edf5bce2fb074 languageName: node linkType: hard @@ -4777,17 +4775,17 @@ __metadata: languageName: node linkType: hard -"@react-native/metro-babel-transformer@npm:0.84.0": - version: 0.84.0 - resolution: "@react-native/metro-babel-transformer@npm:0.84.0" +"@react-native/metro-babel-transformer@npm:0.84.1": + version: 0.84.1 + resolution: "@react-native/metro-babel-transformer@npm:0.84.1" dependencies: "@babel/core": "npm:^7.25.2" - "@react-native/babel-preset": "npm:0.84.0" + "@react-native/babel-preset": "npm:0.84.1" hermes-parser: "npm:0.32.0" nullthrows: "npm:^1.1.1" peerDependencies: "@babel/core": "*" - checksum: 10/acd24a531bf9cf019da0fb112926fdfc083567c45698f073b22185f4a0c70f4e89a5129250d8440513a29c428502bf950a60877f2755f417c54e596ef74d81c3 + checksum: 10/0fc5988b37eb0ef083ab354b5c04a722a44009dd08fa40a7574a630e2f47c19ee34b2da7ee0bd9fab9434515ce4d39ef853fc4d7dc7bab575d71e52d12292e89 languageName: node linkType: hard @@ -4816,14 +4814,14 @@ __metadata: linkType: hard "@react-native/metro-config@npm:^0.84.0": - version: 0.84.0 - resolution: "@react-native/metro-config@npm:0.84.0" + version: 0.84.1 + resolution: "@react-native/metro-config@npm:0.84.1" dependencies: - "@react-native/js-polyfills": "npm:0.84.0" - "@react-native/metro-babel-transformer": "npm:0.84.0" + "@react-native/js-polyfills": "npm:0.84.1" + "@react-native/metro-babel-transformer": "npm:0.84.1" metro-config: "npm:^0.83.3" metro-runtime: "npm:^0.83.3" - checksum: 10/c51fc100d3ecc78ed1b591ba492341bf4ad7b8b0cc8b153f66bf0f3e562ac6afcf5db83b23da030c25c942515d1c61e800ac6b0dc0eea4947d41df97d6081eba + checksum: 10/0637fe16d5e1fa7fcfd729dbfa3842b9ead126c1eb6ebe8ff4bcdc959f92a71bc5e0e9bdeb103a33fb04bb3a53dd3ee219736508acee89963aea6fb9db2901c2 languageName: node linkType: hard @@ -4859,25 +4857,25 @@ __metadata: linkType: hard "@react-navigation/bottom-tabs@npm:^7.4.0": - version: 7.15.2 - resolution: "@react-navigation/bottom-tabs@npm:7.15.2" + version: 7.15.5 + resolution: "@react-navigation/bottom-tabs@npm:7.15.5" dependencies: - "@react-navigation/elements": "npm:^2.9.8" + "@react-navigation/elements": "npm:^2.9.10" color: "npm:^4.2.3" sf-symbols-typescript: "npm:^2.1.0" peerDependencies: - "@react-navigation/native": ^7.1.31 + "@react-navigation/native": ^7.1.33 react: ">= 18.2.0" react-native: "*" react-native-safe-area-context: ">= 4.0.0" react-native-screens: ">= 4.0.0" - checksum: 10/f76179664388dd025c4c6fc5696335407e5769417ffa7a364d601e019e734ac950200071ea417d5de3a1cad26b127251d9237890adbece9ccda56e32c996f807 + checksum: 10/d4cde316c33ba29fae900bcbb8a2e663eade3e72348f332954e9462db3fb433867d86ee493d025cc0ae30910bce5b598844f1097b43b78a4d65dd5d703b00fd5 languageName: node linkType: hard -"@react-navigation/core@npm:^7.15.1": - version: 7.15.1 - resolution: "@react-navigation/core@npm:7.15.1" +"@react-navigation/core@npm:^7.16.1": + version: 7.16.1 + resolution: "@react-navigation/core@npm:7.16.1" dependencies: "@react-navigation/routers": "npm:^7.5.3" escape-string-regexp: "npm:^4.0.0" @@ -4889,73 +4887,73 @@ __metadata: use-sync-external-store: "npm:^1.5.0" peerDependencies: react: ">= 18.2.0" - checksum: 10/308e7c0748827b47870dbac537b545bc7b4b975d64d136ca18c64970449195a6394ec49608e1063a6374c1da31a984b0e06e12de997babaddd201b400f34d37c + checksum: 10/8d6782f1fc99e0b49405c7eab8c5159de0ea69cf787753a3ffc1a05612fff78246d6edd8f5ac0535e631b2dbb326e893f98c9e03733446aef81e16fc3d11dc61 languageName: node linkType: hard "@react-navigation/drawer@npm:^7.8.1": - version: 7.9.2 - resolution: "@react-navigation/drawer@npm:7.9.2" + version: 7.9.4 + resolution: "@react-navigation/drawer@npm:7.9.4" dependencies: - "@react-navigation/elements": "npm:^2.9.8" + "@react-navigation/elements": "npm:^2.9.10" color: "npm:^4.2.3" react-native-drawer-layout: "npm:^4.2.2" use-latest-callback: "npm:^0.2.4" peerDependencies: - "@react-navigation/native": ^7.1.31 + "@react-navigation/native": ^7.1.33 react: ">= 18.2.0" react-native: "*" react-native-gesture-handler: ">= 2.0.0" react-native-reanimated: ">= 2.0.0" react-native-safe-area-context: ">= 4.0.0" react-native-screens: ">= 4.0.0" - checksum: 10/47b37265d0ee005e6194a0353687b8f36ed33c7f3fd059bbb110abcdeed2ab8a3f8e9fdca8875a02a92eae7794035d5887a66c7f862f84077286f2aec2f21e23 + checksum: 10/8f73e1c7c87a030b340c2c5f27788921f3d95681608215581983f0eaa364d1c2cfb513555a87ea374212680d63f23eb635eec5300a135848dce3f41179c24367 languageName: node linkType: hard -"@react-navigation/elements@npm:^2.9.8": - version: 2.9.8 - resolution: "@react-navigation/elements@npm:2.9.8" +"@react-navigation/elements@npm:^2.9.10": + version: 2.9.10 + resolution: "@react-navigation/elements@npm:2.9.10" dependencies: color: "npm:^4.2.3" use-latest-callback: "npm:^0.2.4" use-sync-external-store: "npm:^1.5.0" peerDependencies: "@react-native-masked-view/masked-view": ">= 0.2.0" - "@react-navigation/native": ^7.1.31 + "@react-navigation/native": ^7.1.33 react: ">= 18.2.0" react-native: "*" react-native-safe-area-context: ">= 4.0.0" peerDependenciesMeta: "@react-native-masked-view/masked-view": optional: true - checksum: 10/b34fc3c69f2bbdf18bf9eecf27c866adc806e90f6bb773f35dc35cd8454c822609ced4446c09feb646002a2ee69e6310b28635627c9132081a325e6b1cc370e7 + checksum: 10/ef6b7280d8902411431de1038ed6f19caac5fda9b7e2dff28037d0699cf8fd75eaff07169fc114ff8ec9c1099e1617d75b96dd1b616f3d982c67c86f41a8bc43 languageName: node linkType: hard "@react-navigation/native-stack@npm:^7.3.16": - version: 7.14.2 - resolution: "@react-navigation/native-stack@npm:7.14.2" + version: 7.14.5 + resolution: "@react-navigation/native-stack@npm:7.14.5" dependencies: - "@react-navigation/elements": "npm:^2.9.8" + "@react-navigation/elements": "npm:^2.9.10" color: "npm:^4.2.3" sf-symbols-typescript: "npm:^2.1.0" warn-once: "npm:^0.1.1" peerDependencies: - "@react-navigation/native": ^7.1.31 + "@react-navigation/native": ^7.1.33 react: ">= 18.2.0" react-native: "*" react-native-safe-area-context: ">= 4.0.0" react-native-screens: ">= 4.0.0" - checksum: 10/d1d1b454d57842d87457c1a556b464fa99d3d94948ffde322fe854d7f5c9909d9bd0e9ee7d051376cb9066fd9619934ce468b2809fd4e8aa3d8b6d6a31cfb3e7 + checksum: 10/4c550527d2c00b1765617c3ee91e408c8ef44c467de1d13c1087e69365ee91969c575d4d4561a9f9a57a1650630e955b49a56239eb111a77fb4c6a9b9a4f51a7 languageName: node linkType: hard "@react-navigation/native@npm:^7.1.28, @react-navigation/native@npm:^7.1.8": - version: 7.1.31 - resolution: "@react-navigation/native@npm:7.1.31" + version: 7.1.33 + resolution: "@react-navigation/native@npm:7.1.33" dependencies: - "@react-navigation/core": "npm:^7.15.1" + "@react-navigation/core": "npm:^7.16.1" escape-string-regexp: "npm:^4.0.0" fast-deep-equal: "npm:^3.1.3" nanoid: "npm:^3.3.11" @@ -4963,7 +4961,7 @@ __metadata: peerDependencies: react: ">= 18.2.0" react-native: "*" - checksum: 10/b76efac7073055968e7c3300c6812932c0cdec6c9de1bdae0a80ecbee217c2817b0fa5012cd0817d3f6abe9c198032787bfcee4f4ec080b5827510c11c894cc0 + checksum: 10/97f9345f7a4b0fc39949b56d8b0f85333f0175137e3574b4fd1700eb3f3e3fd2f244ff04a1145096dbdfbdc5c013974984a964a7997088e160f95174380319cf languageName: node linkType: hard @@ -5052,12 +5050,12 @@ __metadata: languageName: node linkType: hard -"@sinonjs/fake-timers@npm:^13.0.0": - version: 13.0.5 - resolution: "@sinonjs/fake-timers@npm:13.0.5" +"@sinonjs/fake-timers@npm:^15.0.0": + version: 15.1.1 + resolution: "@sinonjs/fake-timers@npm:15.1.1" dependencies: "@sinonjs/commons": "npm:^3.0.1" - checksum: 10/11ee417968fc4dce1896ab332ac13f353866075a9d2a88ed1f6258f17cc4f7d93e66031b51fcddb8c203aa4d53fd980b0ae18aba06269f4682164878a992ec3f + checksum: 10/f262d613ea7f7cdb1b5d90c0cae01b7c6b797d6d0f1ca0fe30b7b69012e3076bb8a0f69d735bc69d2824b9bb1efb8554ca9765b4a6bb22defdec9ce79e7cd8a4 languageName: node linkType: hard @@ -5201,13 +5199,6 @@ __metadata: languageName: node linkType: hard -"@trysound/sax@npm:0.2.0": - version: 0.2.0 - resolution: "@trysound/sax@npm:0.2.0" - checksum: 10/7379713eca480ac0d9b6c7b063e06b00a7eac57092354556c81027066eb65b61ea141a69d0cc2e15d32e05b2834d4c9c2184793a5e36bbf5daf05ee5676af18c - languageName: node - linkType: hard - "@tybys/wasm-util@npm:^0.10.0": version: 0.10.1 resolution: "@tybys/wasm-util@npm:0.10.1" @@ -5361,11 +5352,11 @@ __metadata: linkType: hard "@types/node@npm:*": - version: 25.3.1 - resolution: "@types/node@npm:25.3.1" + version: 25.5.0 + resolution: "@types/node@npm:25.5.0" dependencies: undici-types: "npm:~7.18.0" - checksum: 10/7f999e88e7ff3d2e2ee4efbf1de60d9375577d1ad3e1bee01455ec9d8257da2fb1f83dce4cb8fdf9c05dde9fec9e54f61fc9b1731393b53fc74f254712de9f6e + checksum: 10/b1e8116bd8c9ff62e458b76d28a59cf7631537bb17e8961464bf754dd5b07b46f1620f568b2f89970505af9eef478dd74c614651b454c1ea95949ec472c64fcb languageName: node linkType: hard @@ -6056,13 +6047,13 @@ __metadata: linkType: hard "arktype@npm:^2.1.15": - version: 2.1.29 - resolution: "arktype@npm:2.1.29" + version: 2.2.0 + resolution: "arktype@npm:2.2.0" dependencies: "@ark/schema": "npm:0.56.0" "@ark/util": "npm:0.56.0" arkregex: "npm:0.0.5" - checksum: 10/091df54e5df0282a26f5de74cc001569483fc61b3297277a51cb8244f277334a549cf8ae3342ca3bbde95bd10172aaa2f86e6c5738e2853b2b66c088a7c9f398 + checksum: 10/f391a570bd9dd0f39369669ca47c1504fa1969a6f29b60ef1219525c8866ce4b6f8553179b92bf40fc323faa29412ad0b25c03a4a2f16b96f836a1b589aac0b5 languageName: node linkType: hard @@ -6216,20 +6207,20 @@ __metadata: languageName: node linkType: hard -"babel-jest@npm:30.2.0": - version: 30.2.0 - resolution: "babel-jest@npm:30.2.0" +"babel-jest@npm:30.3.0": + version: 30.3.0 + resolution: "babel-jest@npm:30.3.0" dependencies: - "@jest/transform": "npm:30.2.0" + "@jest/transform": "npm:30.3.0" "@types/babel__core": "npm:^7.20.5" babel-plugin-istanbul: "npm:^7.0.1" - babel-preset-jest: "npm:30.2.0" + babel-preset-jest: "npm:30.3.0" chalk: "npm:^4.1.2" graceful-fs: "npm:^4.2.11" slash: "npm:^3.0.0" peerDependencies: "@babel/core": ^7.11.0 || ^8.0.0-0 - checksum: 10/4c7351a366cf8ac2b8a2e4e438867693eb9d83ed24c29c648da4576f700767aaf72a5d14337fc3f92c50b069f5025b26c7b89e3b7b867914b7cf8997fc15f095 + checksum: 10/7c78f083b11430e69e719ddacd4089db3c055437e06b2d7b382d797a675c7a114268f0044ce98c9a32091638cb9ada53e278d46a7079a74ff845d1aa4a2b0678 languageName: node linkType: hard @@ -6276,12 +6267,12 @@ __metadata: languageName: node linkType: hard -"babel-plugin-jest-hoist@npm:30.2.0": - version: 30.2.0 - resolution: "babel-plugin-jest-hoist@npm:30.2.0" +"babel-plugin-jest-hoist@npm:30.3.0": + version: 30.3.0 + resolution: "babel-plugin-jest-hoist@npm:30.3.0" dependencies: "@types/babel__core": "npm:^7.20.5" - checksum: 10/360e87a9aa35f4cf208a10ba79e1821ea906f9e3399db2a9762cbc5076fd59f808e571d88b5b1106738d22e23f9ddefbb8137b2780b2abd401c8573b85c8a2f5 + checksum: 10/1444d633a8ad2505d5e15e458718f1bc5929a074f14179a38f53542c32d3c5158a6f7cab82f7fa6b334b0a45982252639bd7642bb0bc843c6566e44cb083925e languageName: node linkType: hard @@ -6298,15 +6289,15 @@ __metadata: linkType: hard "babel-plugin-polyfill-corejs2@npm:^0.4.14, babel-plugin-polyfill-corejs2@npm:^0.4.15": - version: 0.4.15 - resolution: "babel-plugin-polyfill-corejs2@npm:0.4.15" + version: 0.4.16 + resolution: "babel-plugin-polyfill-corejs2@npm:0.4.16" dependencies: "@babel/compat-data": "npm:^7.28.6" - "@babel/helper-define-polyfill-provider": "npm:^0.6.6" + "@babel/helper-define-polyfill-provider": "npm:^0.6.7" semver: "npm:^6.3.1" peerDependencies: "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - checksum: 10/e5f8a4e716400b2b5c51f7b3c0eec58da92f1d8cc1c6fe2e32555c98bc594be1de7fa1da373f8e42ab098c33867c4cc2931ce648c92aab7a4f4685417707c438 + checksum: 10/0a2e1e7c8bfce0db7062421aabf0c4c874ee4b14e717ff0eeed73f2714ad136bb909efd445bc54f075ecf2e2a32160ab220de3c2d08382e169cbaa8a6108bd13 languageName: node linkType: hard @@ -6323,25 +6314,25 @@ __metadata: linkType: hard "babel-plugin-polyfill-corejs3@npm:^0.14.0": - version: 0.14.0 - resolution: "babel-plugin-polyfill-corejs3@npm:0.14.0" + version: 0.14.1 + resolution: "babel-plugin-polyfill-corejs3@npm:0.14.1" dependencies: - "@babel/helper-define-polyfill-provider": "npm:^0.6.6" + "@babel/helper-define-polyfill-provider": "npm:^0.6.7" core-js-compat: "npm:^3.48.0" peerDependencies: "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - checksum: 10/09c854a3bda9a930fbce4b80d52a24e5b0744fccb0c81bf8f470d62296f197a2afe111b2b9ecb0d8a47068de2f938d14b748295953377e47594b0673d53c9396 + checksum: 10/c92b118f824026f27423610f690609eba8a2b9a178082f5e160a9fe146d858a8153a2c568de3b0409ddb0190d680c73d79d911bc075ce72c79f06b88e022a565 languageName: node linkType: hard "babel-plugin-polyfill-regenerator@npm:^0.6.5, babel-plugin-polyfill-regenerator@npm:^0.6.6": - version: 0.6.6 - resolution: "babel-plugin-polyfill-regenerator@npm:0.6.6" + version: 0.6.7 + resolution: "babel-plugin-polyfill-regenerator@npm:0.6.7" dependencies: - "@babel/helper-define-polyfill-provider": "npm:^0.6.6" + "@babel/helper-define-polyfill-provider": "npm:^0.6.7" peerDependencies: "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - checksum: 10/8de7ea32856e75784601cacf8f4e3cbf04ce1fd05d56614b08b7bbe0674d1e59e37ccaa1c7ed16e3b181a63abe5bd43a1ab0e28b8c95618a9ebf0be5e24d6b25 + checksum: 10/40640a7caa6a7af07fcbedda446c00c057096dc12c142d304cc987af6a2611ee99a9693abdb7c98eccff6889fe9ef352981add970435805f85b9664998bbd416 languageName: node linkType: hard @@ -6461,15 +6452,15 @@ __metadata: languageName: node linkType: hard -"babel-preset-jest@npm:30.2.0": - version: 30.2.0 - resolution: "babel-preset-jest@npm:30.2.0" +"babel-preset-jest@npm:30.3.0": + version: 30.3.0 + resolution: "babel-preset-jest@npm:30.3.0" dependencies: - babel-plugin-jest-hoist: "npm:30.2.0" + babel-plugin-jest-hoist: "npm:30.3.0" babel-preset-current-node-syntax: "npm:^1.2.0" peerDependencies: "@babel/core": ^7.11.0 || ^8.0.0-beta.1 - checksum: 10/f75e155a8cf63ea1c5ca942bf757b934427630a1eeafdf861e9117879b3367931fc521da3c41fd52f8d59d705d1093ffb46c9474b3fd4d765d194bea5659d7d9 + checksum: 10/fd29c8ff5967c047006bde152cf5ac99ce2e1d573f6f044828cb4d06eab95b65549a38554ea97174bbe508006d2a7cb1370581d87aa73f6b3c2134f2d49aaf85 languageName: node linkType: hard @@ -6540,11 +6531,11 @@ __metadata: linkType: hard "baseline-browser-mapping@npm:^2.9.0": - version: 2.10.0 - resolution: "baseline-browser-mapping@npm:2.10.0" + version: 2.10.8 + resolution: "baseline-browser-mapping@npm:2.10.8" bin: baseline-browser-mapping: dist/cli.cjs - checksum: 10/8145e076e4299f04c7a412e6ea63803e330153cd89c47b5303f9b56b58078f4c3d5a5b5332c1069da889e76facacca4d43f8940375f7e73ce0a4d96214332953 + checksum: 10/820972372c87c65c2e665134d70aa44d5722492fb907aa79170fec84086a75de4675f6a7b717cf0a31b4c4f71cd0289b056b71e32007de97a37973a501d31dcb languageName: node linkType: hard @@ -6663,12 +6654,21 @@ __metadata: languageName: node linkType: hard +"brace-expansion@npm:^2.0.2": + version: 2.0.2 + resolution: "brace-expansion@npm:2.0.2" + dependencies: + balanced-match: "npm:^1.0.0" + checksum: 10/01dff195e3646bc4b0d27b63d9bab84d2ebc06121ff5013ad6e5356daa5a9d6b60fa26cf73c74797f2dc3fbec112af13578d51f75228c1112b26c790a87b0488 + languageName: node + linkType: hard + "brace-expansion@npm:^5.0.2": - version: 5.0.3 - resolution: "brace-expansion@npm:5.0.3" + version: 5.0.4 + resolution: "brace-expansion@npm:5.0.4" dependencies: balanced-match: "npm:^4.0.2" - checksum: 10/8ba7deae4ca333d52418d2cde3287ac23f44f7330d92c3ecd96a8941597bea8aab02227bd990944d6711dd549bcc6e550fe70be5d94aa02e2fdc88942f480c9b + checksum: 10/cfd57e20d8ded9578149e47ae4d3fff2b2f78d06b54a32a73057bddff65c8e9b930613f0cbcfefedf12dd117151e19d4da16367d5127c54f3bff02d8a4479bb2 languageName: node linkType: hard @@ -6844,9 +6844,9 @@ __metadata: linkType: hard "caniuse-lite@npm:^1.0.30001759": - version: 1.0.30001774 - resolution: "caniuse-lite@npm:1.0.30001774" - checksum: 10/63c87aeac08548847ecd12746144029761707d9eae57750f673543a2b2a6126bca98584dd551818e8dc2a480d11489bebe0027af26de4ee46466e7b216109862 + version: 1.0.30001779 + resolution: "caniuse-lite@npm:1.0.30001779" + checksum: 10/025b74d98d6bb05d3f95038ad7f9d621fab980768228c166f72bc012ee2f62e65dfc793fa64abab5de079bbfe7f2f831c30cfd22b82335e15107ef4deb27208e languageName: node linkType: hard @@ -7202,13 +7202,12 @@ __metadata: linkType: hard "comment-json@npm:^4.2.5": - version: 4.5.1 - resolution: "comment-json@npm:4.5.1" + version: 4.6.2 + resolution: "comment-json@npm:4.6.2" dependencies: array-timsort: "npm:^1.0.3" - core-util-is: "npm:^1.0.3" esprima: "npm:^4.0.1" - checksum: 10/3bdd2703f26690537f65ef708d62aae3980dba6fc566e82a71d95511b413a6f5f285af9af0415e4739dc6f363db24225e46f5267c576f249100cdb28c3adb00d + checksum: 10/2f6e79b7ae81a919a0dd5fc2fac84e9ebdaa501a5f3ffe5b9f2fd5dbe94d686d8ee9a6bddf5bae364bce104dbb1c599a9d4184aeb211aaf4a283b8900438d242 languageName: node linkType: hard @@ -7324,13 +7323,6 @@ __metadata: languageName: node linkType: hard -"core-util-is@npm:^1.0.3": - version: 1.0.3 - resolution: "core-util-is@npm:1.0.3" - checksum: 10/9de8597363a8e9b9952491ebe18167e3b36e7707569eed0ebf14f8bba773611376466ae34575bca8cfe3c767890c859c74056084738f09d4e4a6f902b2ad7d99 - languageName: node - linkType: hard - "cosmiconfig@npm:^5.0.5": version: 5.2.1 resolution: "cosmiconfig@npm:5.2.1" @@ -7361,8 +7353,8 @@ __metadata: linkType: hard "cosmiconfig@npm:^9.0.0": - version: 9.0.0 - resolution: "cosmiconfig@npm:9.0.0" + version: 9.0.1 + resolution: "cosmiconfig@npm:9.0.1" dependencies: env-paths: "npm:^2.2.1" import-fresh: "npm:^3.3.0" @@ -7373,7 +7365,7 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 10/8bdf1dfbb6fdb3755195b6886dc0649a3c742ec75afa4cb8da7b070936aed22a4f4e5b7359faafe03180358f311dbc300d248fd6586c458203d376a40cc77826 + checksum: 10/89fcac84d062f0710091bb2d6a6175bcde22f5448877db9c43429694408191d3d4e215193b3ac4d54f7f89ef188d55cd481c7a2295b0dc572e65b528bf6fec01 languageName: node linkType: hard @@ -7662,9 +7654,9 @@ __metadata: linkType: hard "dayjs@npm:^1.8.15": - version: 1.11.19 - resolution: "dayjs@npm:1.11.19" - checksum: 10/185b820d68492b83a3ce2b8ddc7543034edc1dfd1423183f6ae4707b29929a3cc56503a81826309279f9084680c15966b99456e74cf41f7d1f6a2f98f9c7196f + version: 1.11.20 + resolution: "dayjs@npm:1.11.20" + checksum: 10/5347533f21a55b8bb1b1ef559be9b805514c3a8fb7e68b75fb7e73808131c59e70909c073aa44ce8a0d159195cd110cdd4081cf87ab96cb06fee3edacae791c6 languageName: node linkType: hard @@ -7728,7 +7720,7 @@ __metadata: languageName: node linkType: hard -"dedent@npm:^1.0.0": +"dedent@npm:^1.0.0, dedent@npm:^1.6.0": version: 1.7.2 resolution: "dedent@npm:1.7.2" peerDependencies: @@ -7740,18 +7732,6 @@ __metadata: languageName: node linkType: hard -"dedent@npm:^1.6.0": - version: 1.7.1 - resolution: "dedent@npm:1.7.1" - peerDependencies: - babel-plugin-macros: ^3.1.0 - peerDependenciesMeta: - babel-plugin-macros: - optional: true - checksum: 10/78785ef592e37e0b1ca7a7a5964c8f3dee1abdff46c5bb49864168579c122328f6bb55c769bc7e005046a7381c3372d3859f0f78ab083950fa146e1c24873f4f - languageName: node - linkType: hard - "deep-extend@npm:^0.6.0": version: 0.6.0 resolution: "deep-extend@npm:0.6.0" @@ -8009,9 +7989,9 @@ __metadata: linkType: hard "electron-to-chromium@npm:^1.5.263": - version: 1.5.302 - resolution: "electron-to-chromium@npm:1.5.302" - checksum: 10/0d31470d04a0d1ea046dd363370081b67e6fe822949b10cfece0a64fd2f8180afb5ccaf14f4294251e444a0af627eb0dc0156242b714c0f10561adf2a21aa5f7 + version: 1.5.313 + resolution: "electron-to-chromium@npm:1.5.313" + checksum: 10/cf23275f15a0bb53ed7811fe75715a4c4bea33cb552caf80f3caf28cb1b46b7299bbc014afc2d4d89d14254723625f1e94be7f4c5a584285f967124c44dcdea4 languageName: node linkType: hard @@ -8208,8 +8188,8 @@ __metadata: linkType: hard "es-iterator-helpers@npm:^1.2.1": - version: 1.2.2 - resolution: "es-iterator-helpers@npm:1.2.2" + version: 1.3.1 + resolution: "es-iterator-helpers@npm:1.3.1" dependencies: call-bind: "npm:^1.0.8" call-bound: "npm:^1.0.4" @@ -8226,8 +8206,9 @@ __metadata: has-symbols: "npm:^1.1.0" internal-slot: "npm:^1.1.0" iterator.prototype: "npm:^1.1.5" + math-intrinsics: "npm:^1.1.0" safe-array-concat: "npm:^1.1.3" - checksum: 10/17b5b2834c4f5719d6ce0e837a4d11c6ba4640bee28290d22ec4daf7106ec3d5fe0ff4f7e5dbaa2b4612e8335934360e964a8f08608d43f2889da106b25481ee + checksum: 10/38106c081426faa6a8c27f44ee653d81350944b449fad81caa032cc02c31280be11fd302d065da3b062534390040c58e8aab55ff897b5ef1ddf060079570c70d languageName: node linkType: hard @@ -8667,17 +8648,17 @@ __metadata: languageName: node linkType: hard -"expect@npm:30.2.0, expect@npm:^30.0.0": - version: 30.2.0 - resolution: "expect@npm:30.2.0" +"expect@npm:30.3.0, expect@npm:^30.0.0": + version: 30.3.0 + resolution: "expect@npm:30.3.0" dependencies: - "@jest/expect-utils": "npm:30.2.0" + "@jest/expect-utils": "npm:30.3.0" "@jest/get-type": "npm:30.1.0" - jest-matcher-utils: "npm:30.2.0" - jest-message-util: "npm:30.2.0" - jest-mock: "npm:30.2.0" - jest-util: "npm:30.2.0" - checksum: 10/cf98ab45ab2e9f2fb9943a3ae0097f72d63a94be179a19fd2818d8fdc3b7681d31cc8ef540606eb8dd967d9c44d73fef263a614e9de260c22943ffb122ad66fd + jest-matcher-utils: "npm:30.3.0" + jest-message-util: "npm:30.3.0" + jest-mock: "npm:30.3.0" + jest-util: "npm:30.3.0" + checksum: 10/607748963fd2cf2b95ec848d59086afdff5e6b690d1ddd907f84514687f32a787896281ba49a5fda2af819238bec7fdeaf258814997d2b08eedc0968de57f3bd languageName: node linkType: hard @@ -9038,10 +9019,12 @@ __metadata: languageName: node linkType: hard -"fast-xml-builder@npm:^1.0.0": - version: 1.0.0 - resolution: "fast-xml-builder@npm:1.0.0" - checksum: 10/06c04d80545e5c9f4d1d6cca00567b5cc09953a92c6328fa48cfb4d7f42630313b8c2bb62e9cb81accee7bb5e1c5312fcae06c3d20dbe52d969a5938233316da +"fast-xml-builder@npm:^1.1.4": + version: 1.1.4 + resolution: "fast-xml-builder@npm:1.1.4" + dependencies: + path-expression-matcher: "npm:^1.1.3" + checksum: 10/32937866aaf5a90e69d1f4ee6e15e875248d5b5d2afd70277e9e8323074de4980cef24575a591b8e43c29f405d5f12377b3bad3842dc412b0c5c17a3eaee4b6b languageName: node linkType: hard @@ -9057,14 +9040,15 @@ __metadata: linkType: hard "fast-xml-parser@npm:^5.3.6": - version: 5.4.1 - resolution: "fast-xml-parser@npm:5.4.1" + version: 5.5.6 + resolution: "fast-xml-parser@npm:5.5.6" dependencies: - fast-xml-builder: "npm:^1.0.0" + fast-xml-builder: "npm:^1.1.4" + path-expression-matcher: "npm:^1.1.3" strnum: "npm:^2.1.2" bin: fxparser: src/cli/cli.js - checksum: 10/2b40067c3ad3542ca197d1353bcb0416cd5db20d5c66d74ac176b99af6ff9bd55a6182d36856a2fd477c95b8fc1f07405475f1662a31185480130ba7076c702a + checksum: 10/91a42a0cf99c83b0e721ceef9c189509e96c91c1875901c6ce6017f78ad25284f646a77a541e96ee45a15c2f13b7780d090c906c3ec3f262db03e7feb1e62315 languageName: node linkType: hard @@ -9189,9 +9173,9 @@ __metadata: linkType: hard "flatted@npm:^3.2.9, flatted@npm:^3.3.1": - version: 3.3.3 - resolution: "flatted@npm:3.3.3" - checksum: 10/8c96c02fbeadcf4e8ffd0fa24983241e27698b0781295622591fc13585e2f226609d95e422bcf2ef044146ffacb6b68b1f20871454eddf75ab3caa6ee5f4a1fe + version: 3.4.1 + resolution: "flatted@npm:3.4.1" + checksum: 10/39a308e2ef82d2d8c80ebc74b67d4ff3f668be168137b649440b6735eb9c115d1e0c13ab0d9958b3d2ea9c85087ab7e14c14aa6f625a22b2916d89bbd91bc4a0 languageName: node linkType: hard @@ -9452,7 +9436,7 @@ __metadata: languageName: node linkType: hard -"glob@npm:^10.3.10, glob@npm:^10.5.0": +"glob@npm:^10.5.0": version: 10.5.0 resolution: "glob@npm:10.5.0" dependencies: @@ -10485,14 +10469,14 @@ __metadata: languageName: node linkType: hard -"jest-changed-files@npm:30.2.0": - version: 30.2.0 - resolution: "jest-changed-files@npm:30.2.0" +"jest-changed-files@npm:30.3.0": + version: 30.3.0 + resolution: "jest-changed-files@npm:30.3.0" dependencies: execa: "npm:^5.1.1" - jest-util: "npm:30.2.0" + jest-util: "npm:30.3.0" p-limit: "npm:^3.1.0" - checksum: 10/ff2275ed5839b88c12ffa66fdc5c17ba02d3e276be6b558bed92872c282d050c3fdd1a275a81187cbe35c16d6d40337b85838772836463c7a2fbd1cba9785ca0 + checksum: 10/a65834a428ec7c4512319af52a7397e5fd90088ca85e649c66cda7092fc287b0fae6c0a9d691cca99278b7dfacbbdbcce17e2bebdd81068503389089035489ce languageName: node linkType: hard @@ -10507,31 +10491,31 @@ __metadata: languageName: node linkType: hard -"jest-circus@npm:30.2.0": - version: 30.2.0 - resolution: "jest-circus@npm:30.2.0" +"jest-circus@npm:30.3.0": + version: 30.3.0 + resolution: "jest-circus@npm:30.3.0" dependencies: - "@jest/environment": "npm:30.2.0" - "@jest/expect": "npm:30.2.0" - "@jest/test-result": "npm:30.2.0" - "@jest/types": "npm:30.2.0" + "@jest/environment": "npm:30.3.0" + "@jest/expect": "npm:30.3.0" + "@jest/test-result": "npm:30.3.0" + "@jest/types": "npm:30.3.0" "@types/node": "npm:*" chalk: "npm:^4.1.2" co: "npm:^4.6.0" dedent: "npm:^1.6.0" is-generator-fn: "npm:^2.1.0" - jest-each: "npm:30.2.0" - jest-matcher-utils: "npm:30.2.0" - jest-message-util: "npm:30.2.0" - jest-runtime: "npm:30.2.0" - jest-snapshot: "npm:30.2.0" - jest-util: "npm:30.2.0" + jest-each: "npm:30.3.0" + jest-matcher-utils: "npm:30.3.0" + jest-message-util: "npm:30.3.0" + jest-runtime: "npm:30.3.0" + jest-snapshot: "npm:30.3.0" + jest-util: "npm:30.3.0" p-limit: "npm:^3.1.0" - pretty-format: "npm:30.2.0" + pretty-format: "npm:30.3.0" pure-rand: "npm:^7.0.0" slash: "npm:^3.0.0" stack-utils: "npm:^2.0.6" - checksum: 10/68bfc65d92385db1017643988215e4ff5af0b10bcab86fb749a063be6bb7d5eb556dc53dd21bedf833a19aa6ae1a781a8d27b2bea25562de02d294b3017435a9 + checksum: 10/6aba7c0282af3db4b03870ebe1fc417e651fbfc3cc260de8b73d95ede3ed390af0c94ef376877c5ef50cf8ab49d125ddcd25d6913543b63bf6caa0e22bfecc6f languageName: node linkType: hard @@ -10563,19 +10547,19 @@ __metadata: languageName: node linkType: hard -"jest-cli@npm:30.2.0": - version: 30.2.0 - resolution: "jest-cli@npm:30.2.0" +"jest-cli@npm:30.3.0": + version: 30.3.0 + resolution: "jest-cli@npm:30.3.0" dependencies: - "@jest/core": "npm:30.2.0" - "@jest/test-result": "npm:30.2.0" - "@jest/types": "npm:30.2.0" + "@jest/core": "npm:30.3.0" + "@jest/test-result": "npm:30.3.0" + "@jest/types": "npm:30.3.0" chalk: "npm:^4.1.2" exit-x: "npm:^0.2.2" import-local: "npm:^3.2.0" - jest-config: "npm:30.2.0" - jest-util: "npm:30.2.0" - jest-validate: "npm:30.2.0" + jest-config: "npm:30.3.0" + jest-util: "npm:30.3.0" + jest-validate: "npm:30.3.0" yargs: "npm:^17.7.2" peerDependencies: node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 @@ -10584,7 +10568,7 @@ __metadata: optional: true bin: jest: ./bin/jest.js - checksum: 10/1cc8304f0e2608801c84cdecce9565a6178f668a6475aed3767a1d82cc539915f98e7404d7c387510313684011dc3095c15397d6725f73aac80fbd96c4155faa + checksum: 10/a80aa3a2eec0b0d6644c25ce196d485e178b9c2ad037c17764a645f2fe156563c7fb2dca07cb10d8b9da77dbb8e0c6bcb4b82ca9a59ee50f12700f06670093c1 languageName: node linkType: hard @@ -10614,32 +10598,31 @@ __metadata: languageName: node linkType: hard -"jest-config@npm:30.2.0": - version: 30.2.0 - resolution: "jest-config@npm:30.2.0" +"jest-config@npm:30.3.0": + version: 30.3.0 + resolution: "jest-config@npm:30.3.0" dependencies: "@babel/core": "npm:^7.27.4" "@jest/get-type": "npm:30.1.0" "@jest/pattern": "npm:30.0.1" - "@jest/test-sequencer": "npm:30.2.0" - "@jest/types": "npm:30.2.0" - babel-jest: "npm:30.2.0" + "@jest/test-sequencer": "npm:30.3.0" + "@jest/types": "npm:30.3.0" + babel-jest: "npm:30.3.0" chalk: "npm:^4.1.2" ci-info: "npm:^4.2.0" deepmerge: "npm:^4.3.1" - glob: "npm:^10.3.10" + glob: "npm:^10.5.0" graceful-fs: "npm:^4.2.11" - jest-circus: "npm:30.2.0" + jest-circus: "npm:30.3.0" jest-docblock: "npm:30.2.0" - jest-environment-node: "npm:30.2.0" + jest-environment-node: "npm:30.3.0" jest-regex-util: "npm:30.0.1" - jest-resolve: "npm:30.2.0" - jest-runner: "npm:30.2.0" - jest-util: "npm:30.2.0" - jest-validate: "npm:30.2.0" - micromatch: "npm:^4.0.8" + jest-resolve: "npm:30.3.0" + jest-runner: "npm:30.3.0" + jest-util: "npm:30.3.0" + jest-validate: "npm:30.3.0" parse-json: "npm:^5.2.0" - pretty-format: "npm:30.2.0" + pretty-format: "npm:30.3.0" slash: "npm:^3.0.0" strip-json-comments: "npm:^3.1.1" peerDependencies: @@ -10653,7 +10636,7 @@ __metadata: optional: true ts-node: optional: true - checksum: 10/296786b0a3d62de77e2f691f208d54ab541c1a73f87747d922eda643c6f25b89125ef3150170c07a6c8a316a30c15428e46237d499f688b0777f38de8a61ad16 + checksum: 10/89c49426e2be5ee0c7cf9d6ab0a1dd6eb5ea03f67a5cc57d991d3d2441762d7101a215da5596bcb5b39c47e209ab8fdf4682fd1365cef7a5e48903b689bf4116 languageName: node linkType: hard @@ -10695,15 +10678,15 @@ __metadata: languageName: node linkType: hard -"jest-diff@npm:30.2.0": - version: 30.2.0 - resolution: "jest-diff@npm:30.2.0" +"jest-diff@npm:30.3.0": + version: 30.3.0 + resolution: "jest-diff@npm:30.3.0" dependencies: - "@jest/diff-sequences": "npm:30.0.1" + "@jest/diff-sequences": "npm:30.3.0" "@jest/get-type": "npm:30.1.0" chalk: "npm:^4.1.2" - pretty-format: "npm:30.2.0" - checksum: 10/1fb9e4fb7dff81814b4f69eaa7db28e184d62306a3a8ea2447d02ca53d2cfa771e83ede513f67ec5239dffacfaac32ff2b49866d211e4c7516f51c1fc06ede42 + pretty-format: "npm:30.3.0" + checksum: 10/9f566259085e6badd525dc48ee6de3792cfae080abd66e170ac230359cf32c4334d92f0f48b577a31ad2a6aed4aefde81f5f4366ab44a96f78bcde975e5cc26e languageName: node linkType: hard @@ -10737,16 +10720,16 @@ __metadata: languageName: node linkType: hard -"jest-each@npm:30.2.0": - version: 30.2.0 - resolution: "jest-each@npm:30.2.0" +"jest-each@npm:30.3.0": + version: 30.3.0 + resolution: "jest-each@npm:30.3.0" dependencies: "@jest/get-type": "npm:30.1.0" - "@jest/types": "npm:30.2.0" + "@jest/types": "npm:30.3.0" chalk: "npm:^4.1.2" - jest-util: "npm:30.2.0" - pretty-format: "npm:30.2.0" - checksum: 10/f95e7dc1cef4b6a77899325702a214834ae25d01276cc31279654dc7e04f63c1925a37848dd16a0d16508c0fd3d182145f43c10af93952b7a689df3aeac198e9 + jest-util: "npm:30.3.0" + pretty-format: "npm:30.3.0" + checksum: 10/ece465cbb1c4fbb445c9cfacd33275489940684fd0d447f6d4bdb4ef81d63c1b0bc3b365be7400dbbffd8d5502fd5faf10e97025a61c27bcd3da1ea21c749381 languageName: node linkType: hard @@ -10763,18 +10746,18 @@ __metadata: languageName: node linkType: hard -"jest-environment-node@npm:30.2.0": - version: 30.2.0 - resolution: "jest-environment-node@npm:30.2.0" +"jest-environment-node@npm:30.3.0": + version: 30.3.0 + resolution: "jest-environment-node@npm:30.3.0" dependencies: - "@jest/environment": "npm:30.2.0" - "@jest/fake-timers": "npm:30.2.0" - "@jest/types": "npm:30.2.0" + "@jest/environment": "npm:30.3.0" + "@jest/fake-timers": "npm:30.3.0" + "@jest/types": "npm:30.3.0" "@types/node": "npm:*" - jest-mock: "npm:30.2.0" - jest-util: "npm:30.2.0" - jest-validate: "npm:30.2.0" - checksum: 10/7918bfea7367bd3e12dbbc4ea5afb193b5c47e480a6d1382512f051e2f028458fc9f5ef2f6260737ad41a0b1894661790ff3aaf3cbb4148a33ce2ce7aec64847 + jest-mock: "npm:30.3.0" + jest-util: "npm:30.3.0" + jest-validate: "npm:30.3.0" + checksum: 10/805732507857f283f8c5eaca78561401c16043cd9a2579fc4a3cd6139a5138c6108f4b32f7fafe5b41f9b53f2fbc63cf65eb892e15e086034b09899c9fa4fed4 languageName: node linkType: hard @@ -10799,25 +10782,25 @@ __metadata: languageName: node linkType: hard -"jest-haste-map@npm:30.2.0": - version: 30.2.0 - resolution: "jest-haste-map@npm:30.2.0" +"jest-haste-map@npm:30.3.0": + version: 30.3.0 + resolution: "jest-haste-map@npm:30.3.0" dependencies: - "@jest/types": "npm:30.2.0" + "@jest/types": "npm:30.3.0" "@types/node": "npm:*" anymatch: "npm:^3.1.3" fb-watchman: "npm:^2.0.2" fsevents: "npm:^2.3.3" graceful-fs: "npm:^4.2.11" jest-regex-util: "npm:30.0.1" - jest-util: "npm:30.2.0" - jest-worker: "npm:30.2.0" - micromatch: "npm:^4.0.8" + jest-util: "npm:30.3.0" + jest-worker: "npm:30.3.0" + picomatch: "npm:^4.0.3" walker: "npm:^1.0.8" dependenciesMeta: fsevents: optional: true - checksum: 10/a88be6b0b672144aa30fe2d72e630d639c8d8729ee2cef84d0f830eac2005ac021cd8354f8ed8ecd74223f6a8b281efb62f466f5c9e01ed17650e38761051f4c + checksum: 10/0e0cc449d57414ac2d1f9ece64a98ffc4b4041fe3fba7cf9aaeb71089f7101583b1752e88aa4440d6fa71f86ef50d630be4f31f922cdf404d78655cb9811493b languageName: node linkType: hard @@ -10844,13 +10827,13 @@ __metadata: languageName: node linkType: hard -"jest-leak-detector@npm:30.2.0": - version: 30.2.0 - resolution: "jest-leak-detector@npm:30.2.0" +"jest-leak-detector@npm:30.3.0": + version: 30.3.0 + resolution: "jest-leak-detector@npm:30.3.0" dependencies: "@jest/get-type": "npm:30.1.0" - pretty-format: "npm:30.2.0" - checksum: 10/c430d6ed7910b2174738fbdca4ea64cbfe805216414c0d143c1090148f1389fec99d0733c0a8ed0a86709c89b4a4085b4749ac3a2cbc7deaf3ca87457afd24fc + pretty-format: "npm:30.3.0" + checksum: 10/950ce3266067dd983f80231ce753fdfb9fe167d810b4507d84e674205c2cb96d37f38615ae502fa9277dde497ee52ce581656b48709aacf9502a4f0006bfab0e languageName: node linkType: hard @@ -10864,15 +10847,15 @@ __metadata: languageName: node linkType: hard -"jest-matcher-utils@npm:30.2.0": - version: 30.2.0 - resolution: "jest-matcher-utils@npm:30.2.0" +"jest-matcher-utils@npm:30.3.0": + version: 30.3.0 + resolution: "jest-matcher-utils@npm:30.3.0" dependencies: "@jest/get-type": "npm:30.1.0" chalk: "npm:^4.1.2" - jest-diff: "npm:30.2.0" - pretty-format: "npm:30.2.0" - checksum: 10/f3f1ecf68ca63c9d1d80a175637a8fc655edfd1ee83220f6e3f6bd464ecbe2f93148fdd440a5a5e5a2b0b2cc8ee84ddc3dcef58a6dbc66821c792f48d260c6d4 + jest-diff: "npm:30.3.0" + pretty-format: "npm:30.3.0" + checksum: 10/8aeef24fe2a21a3a22eb26a805c0a4c8ca8961aa1ebc07d680bf55b260f593814467bdfb60b271a3c239a411b2468f352c279cef466e35fd024d901ffa6cc942 languageName: node linkType: hard @@ -10888,20 +10871,20 @@ __metadata: languageName: node linkType: hard -"jest-message-util@npm:30.2.0": - version: 30.2.0 - resolution: "jest-message-util@npm:30.2.0" +"jest-message-util@npm:30.3.0": + version: 30.3.0 + resolution: "jest-message-util@npm:30.3.0" dependencies: "@babel/code-frame": "npm:^7.27.1" - "@jest/types": "npm:30.2.0" + "@jest/types": "npm:30.3.0" "@types/stack-utils": "npm:^2.0.3" chalk: "npm:^4.1.2" graceful-fs: "npm:^4.2.11" - micromatch: "npm:^4.0.8" - pretty-format: "npm:30.2.0" + picomatch: "npm:^4.0.3" + pretty-format: "npm:30.3.0" slash: "npm:^3.0.0" stack-utils: "npm:^2.0.6" - checksum: 10/e29ec76e8c8e4da5f5b25198be247535626ccf3a940e93fdd51fc6a6bcf70feaa2921baae3806182a090431d90b08c939eb13fb64249b171d2e9ae3a452a8fd2 + checksum: 10/886577543ec60b421d21987190c5e393ff3652f4f2f2b504776d73f932518827b026ab8e6ffdb1f21ff5142ddf160ba4794e56d96143baeb4ae6939e040a10bd languageName: node linkType: hard @@ -10922,14 +10905,14 @@ __metadata: languageName: node linkType: hard -"jest-mock@npm:30.2.0": - version: 30.2.0 - resolution: "jest-mock@npm:30.2.0" +"jest-mock@npm:30.3.0": + version: 30.3.0 + resolution: "jest-mock@npm:30.3.0" dependencies: - "@jest/types": "npm:30.2.0" + "@jest/types": "npm:30.3.0" "@types/node": "npm:*" - jest-util: "npm:30.2.0" - checksum: 10/cde9b56805f90bf811a9231873ee88a0fb83bf4bf50972ae76960725da65220fcb119688f2e90e1ef33fbfd662194858d7f43809d881f1c41bb55d94e62adeab + jest-util: "npm:30.3.0" + checksum: 10/9d2a9e52c2aebc486e9accaf641efa5c6589666e883b5ac1987261d0e2c105a06b885c22aeeb1cd7582e421970c95e34fe0b41bc4a8c06d7e3e4c27651e76ad1 languageName: node linkType: hard @@ -10970,13 +10953,13 @@ __metadata: languageName: node linkType: hard -"jest-resolve-dependencies@npm:30.2.0": - version: 30.2.0 - resolution: "jest-resolve-dependencies@npm:30.2.0" +"jest-resolve-dependencies@npm:30.3.0": + version: 30.3.0 + resolution: "jest-resolve-dependencies@npm:30.3.0" dependencies: jest-regex-util: "npm:30.0.1" - jest-snapshot: "npm:30.2.0" - checksum: 10/0ff1a574f8c07f2e54a4ac8ab17aea00dfe2982e99b03fbd44f4211a94b8e5a59fdc43a59f9d6c0578a10a7b56a0611ad5ab40e4893973ff3f40dd414433b194 + jest-snapshot: "npm:30.3.0" + checksum: 10/79dfbc3c8c967e7908bcb02f5116c37002f2cdc10360d179876de832c10ee87cb85cc27895b035697da477ab6ad70170f4e2907a85d35a44117646554cc72111 languageName: node linkType: hard @@ -10990,19 +10973,19 @@ __metadata: languageName: node linkType: hard -"jest-resolve@npm:30.2.0": - version: 30.2.0 - resolution: "jest-resolve@npm:30.2.0" +"jest-resolve@npm:30.3.0": + version: 30.3.0 + resolution: "jest-resolve@npm:30.3.0" dependencies: chalk: "npm:^4.1.2" graceful-fs: "npm:^4.2.11" - jest-haste-map: "npm:30.2.0" + jest-haste-map: "npm:30.3.0" jest-pnp-resolver: "npm:^1.2.3" - jest-util: "npm:30.2.0" - jest-validate: "npm:30.2.0" + jest-util: "npm:30.3.0" + jest-validate: "npm:30.3.0" slash: "npm:^3.0.0" unrs-resolver: "npm:^1.7.11" - checksum: 10/e1f03da6811a946f5d885ea739a973975d099cc760641f9e1f90ac9c6621408538ba1e909f789d45d6e8d2411b78fb09230f16f15669621aa407aed7511fdf01 + checksum: 10/7d88ef3f6424386e4b4e65d486ac1d3b86c142cf789f0ab945a2cd8bd830edc0314c7561a459b95062f41bc550ae7110f461dbafcc07030f61728edb00b4bcdd languageName: node linkType: hard @@ -11023,33 +11006,33 @@ __metadata: languageName: node linkType: hard -"jest-runner@npm:30.2.0": - version: 30.2.0 - resolution: "jest-runner@npm:30.2.0" +"jest-runner@npm:30.3.0": + version: 30.3.0 + resolution: "jest-runner@npm:30.3.0" dependencies: - "@jest/console": "npm:30.2.0" - "@jest/environment": "npm:30.2.0" - "@jest/test-result": "npm:30.2.0" - "@jest/transform": "npm:30.2.0" - "@jest/types": "npm:30.2.0" + "@jest/console": "npm:30.3.0" + "@jest/environment": "npm:30.3.0" + "@jest/test-result": "npm:30.3.0" + "@jest/transform": "npm:30.3.0" + "@jest/types": "npm:30.3.0" "@types/node": "npm:*" chalk: "npm:^4.1.2" emittery: "npm:^0.13.1" exit-x: "npm:^0.2.2" graceful-fs: "npm:^4.2.11" jest-docblock: "npm:30.2.0" - jest-environment-node: "npm:30.2.0" - jest-haste-map: "npm:30.2.0" - jest-leak-detector: "npm:30.2.0" - jest-message-util: "npm:30.2.0" - jest-resolve: "npm:30.2.0" - jest-runtime: "npm:30.2.0" - jest-util: "npm:30.2.0" - jest-watcher: "npm:30.2.0" - jest-worker: "npm:30.2.0" + jest-environment-node: "npm:30.3.0" + jest-haste-map: "npm:30.3.0" + jest-leak-detector: "npm:30.3.0" + jest-message-util: "npm:30.3.0" + jest-resolve: "npm:30.3.0" + jest-runtime: "npm:30.3.0" + jest-util: "npm:30.3.0" + jest-watcher: "npm:30.3.0" + jest-worker: "npm:30.3.0" p-limit: "npm:^3.1.0" source-map-support: "npm:0.5.13" - checksum: 10/d3706aa70e64a7ef8b38360d34ea6c261ba4d0b42136d7fb603c4fa71c24fa81f22c39ed2e39ee0db2363a42827810291f3ceb6a299e5996b41d701ad9b24184 + checksum: 10/f467591d2ff95f7b3138dc7c8631e751000d1fcabfdb9a94623fce3fd7b538a45628e9a1e8e8758c4d7a0c3757c393a3ef034ba986d7565e3f1b597ab7a73748 languageName: node linkType: hard @@ -11082,33 +11065,33 @@ __metadata: languageName: node linkType: hard -"jest-runtime@npm:30.2.0": - version: 30.2.0 - resolution: "jest-runtime@npm:30.2.0" +"jest-runtime@npm:30.3.0": + version: 30.3.0 + resolution: "jest-runtime@npm:30.3.0" dependencies: - "@jest/environment": "npm:30.2.0" - "@jest/fake-timers": "npm:30.2.0" - "@jest/globals": "npm:30.2.0" + "@jest/environment": "npm:30.3.0" + "@jest/fake-timers": "npm:30.3.0" + "@jest/globals": "npm:30.3.0" "@jest/source-map": "npm:30.0.1" - "@jest/test-result": "npm:30.2.0" - "@jest/transform": "npm:30.2.0" - "@jest/types": "npm:30.2.0" + "@jest/test-result": "npm:30.3.0" + "@jest/transform": "npm:30.3.0" + "@jest/types": "npm:30.3.0" "@types/node": "npm:*" chalk: "npm:^4.1.2" cjs-module-lexer: "npm:^2.1.0" collect-v8-coverage: "npm:^1.0.2" - glob: "npm:^10.3.10" + glob: "npm:^10.5.0" graceful-fs: "npm:^4.2.11" - jest-haste-map: "npm:30.2.0" - jest-message-util: "npm:30.2.0" - jest-mock: "npm:30.2.0" + jest-haste-map: "npm:30.3.0" + jest-message-util: "npm:30.3.0" + jest-mock: "npm:30.3.0" jest-regex-util: "npm:30.0.1" - jest-resolve: "npm:30.2.0" - jest-snapshot: "npm:30.2.0" - jest-util: "npm:30.2.0" + jest-resolve: "npm:30.3.0" + jest-snapshot: "npm:30.3.0" + jest-util: "npm:30.3.0" slash: "npm:^3.0.0" strip-bom: "npm:^4.0.0" - checksum: 10/81a3a9951420863f001e74c510bf35b85ae983f636f43ee1ffa1618b5a8ddafb681bc2810f71814bc8c8373e9593c89576b2325daf3c765e50057e48d5941df3 + checksum: 10/a9335405ca46e8d77c8400887566b5cf2a3544e1b067eb3b187e86ea5c74f1b8b16ecf1de3a589bfb32be95e77452a01913f187d66a41c5a4595a30d7dc1daf0 languageName: node linkType: hard @@ -11142,32 +11125,32 @@ __metadata: languageName: node linkType: hard -"jest-snapshot@npm:30.2.0": - version: 30.2.0 - resolution: "jest-snapshot@npm:30.2.0" +"jest-snapshot@npm:30.3.0": + version: 30.3.0 + resolution: "jest-snapshot@npm:30.3.0" dependencies: "@babel/core": "npm:^7.27.4" "@babel/generator": "npm:^7.27.5" "@babel/plugin-syntax-jsx": "npm:^7.27.1" "@babel/plugin-syntax-typescript": "npm:^7.27.1" "@babel/types": "npm:^7.27.3" - "@jest/expect-utils": "npm:30.2.0" + "@jest/expect-utils": "npm:30.3.0" "@jest/get-type": "npm:30.1.0" - "@jest/snapshot-utils": "npm:30.2.0" - "@jest/transform": "npm:30.2.0" - "@jest/types": "npm:30.2.0" + "@jest/snapshot-utils": "npm:30.3.0" + "@jest/transform": "npm:30.3.0" + "@jest/types": "npm:30.3.0" babel-preset-current-node-syntax: "npm:^1.2.0" chalk: "npm:^4.1.2" - expect: "npm:30.2.0" + expect: "npm:30.3.0" graceful-fs: "npm:^4.2.11" - jest-diff: "npm:30.2.0" - jest-matcher-utils: "npm:30.2.0" - jest-message-util: "npm:30.2.0" - jest-util: "npm:30.2.0" - pretty-format: "npm:30.2.0" + jest-diff: "npm:30.3.0" + jest-matcher-utils: "npm:30.3.0" + jest-message-util: "npm:30.3.0" + jest-util: "npm:30.3.0" + pretty-format: "npm:30.3.0" semver: "npm:^7.7.2" synckit: "npm:^0.11.8" - checksum: 10/119390b49f397ed622ba7c375fc15f97af67c4fc49a34cf829c86ee732be2b06ad3c7171c76bb842a0e84a234783f1a4c721909aa316fbe00c6abc7c5962dfbc + checksum: 10/d9f75c436587410cc8170a710d53a632e148a648ec82476ef9e618d8067246e48af7c460773304ad53eecf748b118619a6afd87212f86d680d3439787b4fec39 languageName: node linkType: hard @@ -11199,17 +11182,17 @@ __metadata: languageName: node linkType: hard -"jest-util@npm:30.2.0": - version: 30.2.0 - resolution: "jest-util@npm:30.2.0" +"jest-util@npm:30.3.0": + version: 30.3.0 + resolution: "jest-util@npm:30.3.0" dependencies: - "@jest/types": "npm:30.2.0" + "@jest/types": "npm:30.3.0" "@types/node": "npm:*" chalk: "npm:^4.1.2" ci-info: "npm:^4.2.0" graceful-fs: "npm:^4.2.11" - picomatch: "npm:^4.0.2" - checksum: 10/cf2f2fb83417ea69f9992121561c95cf4e9aad7946819b771b8b52addf78811101b33b51d0a39fa0c305f2751dab262feed7699de052659ff03d51827c8862f5 + picomatch: "npm:^4.0.3" + checksum: 10/4b016004637f6a53d6f54c993dc8904a4d6abe93acb8dd70622dc2ca80290a03692e834af1068969b486426e87d411144705edd4d772bb715a826d7e15b5a4b3 languageName: node linkType: hard @@ -11227,17 +11210,17 @@ __metadata: languageName: node linkType: hard -"jest-validate@npm:30.2.0": - version: 30.2.0 - resolution: "jest-validate@npm:30.2.0" +"jest-validate@npm:30.3.0": + version: 30.3.0 + resolution: "jest-validate@npm:30.3.0" dependencies: "@jest/get-type": "npm:30.1.0" - "@jest/types": "npm:30.2.0" + "@jest/types": "npm:30.3.0" camelcase: "npm:^6.3.0" chalk: "npm:^4.1.2" leven: "npm:^3.1.0" - pretty-format: "npm:30.2.0" - checksum: 10/61e66c6df29a1e181f8de063678dd2096bb52cc8a8ead3c9a3f853d54eca458ad04c7fb81931d9274affb67d0504a91a2a520456a139a26665810c3bf039b677 + pretty-format: "npm:30.3.0" + checksum: 10/b26e32602c65f93d4fa9ca24efa661df24b8919c5c4cb88b87852178310833df3a7fdb757afb9d769cfe13f6636385626d8ac8a2ad7af47365d309a548cd0e06 languageName: node linkType: hard @@ -11255,19 +11238,19 @@ __metadata: languageName: node linkType: hard -"jest-watcher@npm:30.2.0": - version: 30.2.0 - resolution: "jest-watcher@npm:30.2.0" +"jest-watcher@npm:30.3.0": + version: 30.3.0 + resolution: "jest-watcher@npm:30.3.0" dependencies: - "@jest/test-result": "npm:30.2.0" - "@jest/types": "npm:30.2.0" + "@jest/test-result": "npm:30.3.0" + "@jest/types": "npm:30.3.0" "@types/node": "npm:*" ansi-escapes: "npm:^4.3.2" chalk: "npm:^4.1.2" emittery: "npm:^0.13.1" - jest-util: "npm:30.2.0" + jest-util: "npm:30.3.0" string-length: "npm:^4.0.2" - checksum: 10/fa38d06dcc59dbbd6a9ff22dea499d3c81ed376d9993b82d01797a99bf466d48641a99b9f3670a4b5480ca31144c5e017b96b7059e4d7541358fb48cf517a2db + checksum: 10/b3a284869be1c69a8084c1129fcc08b719b8556d3af93b6cd587f9e2f948e5ce5084cb0ec62a166e3161d1d8b6dc580a88ba02abc05a0948809c65b27bd60f3a languageName: node linkType: hard @@ -11287,16 +11270,16 @@ __metadata: languageName: node linkType: hard -"jest-worker@npm:30.2.0": - version: 30.2.0 - resolution: "jest-worker@npm:30.2.0" +"jest-worker@npm:30.3.0": + version: 30.3.0 + resolution: "jest-worker@npm:30.3.0" dependencies: "@types/node": "npm:*" "@ungap/structured-clone": "npm:^1.3.0" - jest-util: "npm:30.2.0" + jest-util: "npm:30.3.0" merge-stream: "npm:^2.0.0" supports-color: "npm:^8.1.1" - checksum: 10/9354b0c71c80173f673da6bbc0ddaad26e4395b06532f7332e0c1e93e855b873b10139b040e01eda77f3dc5a0b67613e2bd7c56c4947ee771acfc3611de2ca29 + checksum: 10/6198e7462617e8f544b1ba593970fb7656e990aa87a2259f693edde106b5aecf63bae692e8d6adc4313efcaba283b15fc25f6834cacca12cf241da0ece722060 languageName: node linkType: hard @@ -11332,13 +11315,13 @@ __metadata: linkType: hard "jest@npm:^30.2.0": - version: 30.2.0 - resolution: "jest@npm:30.2.0" + version: 30.3.0 + resolution: "jest@npm:30.3.0" dependencies: - "@jest/core": "npm:30.2.0" - "@jest/types": "npm:30.2.0" + "@jest/core": "npm:30.3.0" + "@jest/types": "npm:30.3.0" import-local: "npm:^3.2.0" - jest-cli: "npm:30.2.0" + jest-cli: "npm:30.3.0" peerDependencies: node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 peerDependenciesMeta: @@ -11346,7 +11329,7 @@ __metadata: optional: true bin: jest: ./bin/jest.js - checksum: 10/61c9d100750e4354cd7305d1f3ba253ffde4deaf12cb4be4d42d54f2dd5986e383a39c4a8691dbdc3839c69094a52413ed36f1886540ac37b71914a990b810d0 + checksum: 10/e8485ede8456c71915e94a7ab4fe66c983043263109d61e0665a17cb7f8e843a5a30abca4d932b0ea7aa90326aa10d4acb31d8f3cd2b3158a89c1e5ee3b92856 languageName: node linkType: hard @@ -11493,11 +11476,11 @@ __metadata: linkType: hard "jsonrepair@npm:^3.12.0": - version: 3.13.2 - resolution: "jsonrepair@npm:3.13.2" + version: 3.13.3 + resolution: "jsonrepair@npm:3.13.3" bin: jsonrepair: bin/cli.js - checksum: 10/c0c1ec46af1d8e396c22e4d41e4d85eff2c713afdb71b6634a72ed4f356b79259affadb4e79b31042e4c1634313ddc0b3d7960fd74f5fba918198ecc74fe5e82 + checksum: 10/cd1d42516e3e03ccc44498c328f87f4ec05b24afe190becced0babf5d608e81b375e8d2040494142760556c1d6583b395073b5253626907e4df968d8cf01115c languageName: node linkType: hard @@ -11589,99 +11572,99 @@ __metadata: languageName: node linkType: hard -"lightningcss-android-arm64@npm:1.31.1": - version: 1.31.1 - resolution: "lightningcss-android-arm64@npm:1.31.1" +"lightningcss-android-arm64@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-android-arm64@npm:1.32.0" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"lightningcss-darwin-arm64@npm:1.31.1": - version: 1.31.1 - resolution: "lightningcss-darwin-arm64@npm:1.31.1" +"lightningcss-darwin-arm64@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-darwin-arm64@npm:1.32.0" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"lightningcss-darwin-x64@npm:1.31.1": - version: 1.31.1 - resolution: "lightningcss-darwin-x64@npm:1.31.1" +"lightningcss-darwin-x64@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-darwin-x64@npm:1.32.0" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"lightningcss-freebsd-x64@npm:1.31.1": - version: 1.31.1 - resolution: "lightningcss-freebsd-x64@npm:1.31.1" +"lightningcss-freebsd-x64@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-freebsd-x64@npm:1.32.0" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"lightningcss-linux-arm-gnueabihf@npm:1.31.1": - version: 1.31.1 - resolution: "lightningcss-linux-arm-gnueabihf@npm:1.31.1" +"lightningcss-linux-arm-gnueabihf@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-linux-arm-gnueabihf@npm:1.32.0" conditions: os=linux & cpu=arm languageName: node linkType: hard -"lightningcss-linux-arm64-gnu@npm:1.31.1": - version: 1.31.1 - resolution: "lightningcss-linux-arm64-gnu@npm:1.31.1" +"lightningcss-linux-arm64-gnu@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-linux-arm64-gnu@npm:1.32.0" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"lightningcss-linux-arm64-musl@npm:1.31.1": - version: 1.31.1 - resolution: "lightningcss-linux-arm64-musl@npm:1.31.1" +"lightningcss-linux-arm64-musl@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-linux-arm64-musl@npm:1.32.0" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"lightningcss-linux-x64-gnu@npm:1.31.1": - version: 1.31.1 - resolution: "lightningcss-linux-x64-gnu@npm:1.31.1" +"lightningcss-linux-x64-gnu@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-linux-x64-gnu@npm:1.32.0" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"lightningcss-linux-x64-musl@npm:1.31.1": - version: 1.31.1 - resolution: "lightningcss-linux-x64-musl@npm:1.31.1" +"lightningcss-linux-x64-musl@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-linux-x64-musl@npm:1.32.0" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"lightningcss-win32-arm64-msvc@npm:1.31.1": - version: 1.31.1 - resolution: "lightningcss-win32-arm64-msvc@npm:1.31.1" +"lightningcss-win32-arm64-msvc@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-win32-arm64-msvc@npm:1.32.0" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"lightningcss-win32-x64-msvc@npm:1.31.1": - version: 1.31.1 - resolution: "lightningcss-win32-x64-msvc@npm:1.31.1" +"lightningcss-win32-x64-msvc@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-win32-x64-msvc@npm:1.32.0" conditions: os=win32 & cpu=x64 languageName: node linkType: hard "lightningcss@npm:^1.30.1": - version: 1.31.1 - resolution: "lightningcss@npm:1.31.1" + version: 1.32.0 + resolution: "lightningcss@npm:1.32.0" dependencies: detect-libc: "npm:^2.0.3" - lightningcss-android-arm64: "npm:1.31.1" - lightningcss-darwin-arm64: "npm:1.31.1" - lightningcss-darwin-x64: "npm:1.31.1" - lightningcss-freebsd-x64: "npm:1.31.1" - lightningcss-linux-arm-gnueabihf: "npm:1.31.1" - lightningcss-linux-arm64-gnu: "npm:1.31.1" - lightningcss-linux-arm64-musl: "npm:1.31.1" - lightningcss-linux-x64-gnu: "npm:1.31.1" - lightningcss-linux-x64-musl: "npm:1.31.1" - lightningcss-win32-arm64-msvc: "npm:1.31.1" - lightningcss-win32-x64-msvc: "npm:1.31.1" + lightningcss-android-arm64: "npm:1.32.0" + lightningcss-darwin-arm64: "npm:1.32.0" + lightningcss-darwin-x64: "npm:1.32.0" + lightningcss-freebsd-x64: "npm:1.32.0" + lightningcss-linux-arm-gnueabihf: "npm:1.32.0" + lightningcss-linux-arm64-gnu: "npm:1.32.0" + lightningcss-linux-arm64-musl: "npm:1.32.0" + lightningcss-linux-x64-gnu: "npm:1.32.0" + lightningcss-linux-x64-musl: "npm:1.32.0" + lightningcss-win32-arm64-msvc: "npm:1.32.0" + lightningcss-win32-x64-msvc: "npm:1.32.0" dependenciesMeta: lightningcss-android-arm64: optional: true @@ -11705,7 +11688,7 @@ __metadata: optional: true lightningcss-win32-x64-msvc: optional: true - checksum: 10/3c2b2c2f648b12d9cba623d2e558f74fcce35911077e3d33f97ed521e0ad7a84e2c814628f6e16f64095c4483f6b180dee7b2e441b3ff5f44d142a510785a0c6 + checksum: 10/098e61007f0d0ec8b5c50884e33b543b551d1ff21bc7b062434b6638fd0b8596858f823b60dfc2a4aa756f3cb120ad79f2b7f4a55b1bda2c0269ab8cf476f114 languageName: node linkType: hard @@ -11869,9 +11852,9 @@ __metadata: linkType: hard "lru-cache@npm:^11.0.0, lru-cache@npm:^11.1.0, lru-cache@npm:^11.2.1": - version: 11.2.6 - resolution: "lru-cache@npm:11.2.6" - checksum: 10/91222bbd59f793a0a0ad57789388f06b34ac9bb1613433c1d1810457d09db5cd3ec8943227ce2e1f5d6a0a15d6f1a9f129cb2c49ae9b6b10e82d4965fddecbef + version: 11.2.7 + resolution: "lru-cache@npm:11.2.7" + checksum: 10/fbff4b8dee8189dde9b52cdfb3ea89b4c9cec094c1538cd30d1f47299477ff312efdb35f7994477ec72328f8e754e232b26a143feda1bd1f79ff22da6664d2c5 languageName: node linkType: hard @@ -12086,15 +12069,15 @@ __metadata: languageName: node linkType: hard -"metro-babel-transformer@npm:0.83.4": - version: 0.83.4 - resolution: "metro-babel-transformer@npm:0.83.4" +"metro-babel-transformer@npm:0.83.5": + version: 0.83.5 + resolution: "metro-babel-transformer@npm:0.83.5" dependencies: "@babel/core": "npm:^7.25.2" flow-enums-runtime: "npm:^0.0.6" hermes-parser: "npm:0.33.3" nullthrows: "npm:^1.1.1" - checksum: 10/931bbb895bbf6f9feec6faaac425b7f85365ff9e1c5d2019b3310bc977354b6b804365562071510fbcd9ec079a934ee416cf315d954bb4bb1f31973bfd0e2956 + checksum: 10/2a7664a55a5c3f276c884288978bf2fb4d5f5a5137f3769d5fdfd79d6a2f0027475b0d8a19ff1d8b3d39b91f4bb7c54dbd191f7d671d776ccd4a84183f69aee2 languageName: node linkType: hard @@ -12116,12 +12099,12 @@ __metadata: languageName: node linkType: hard -"metro-cache-key@npm:0.83.4": - version: 0.83.4 - resolution: "metro-cache-key@npm:0.83.4" +"metro-cache-key@npm:0.83.5": + version: 0.83.5 + resolution: "metro-cache-key@npm:0.83.5" dependencies: flow-enums-runtime: "npm:^0.0.6" - checksum: 10/fb1b3dc077185454dbf981981da98cbf7af9aa16982bac6dac1eec96cf1023cec05985e011ddf15ab47a052f0829070a40450f867964e28bad677d13456a5480 + checksum: 10/704d0d8e06e8477d20c700cd5f729356aaa704999d4b80882b85aa21ccf7da13959dcd0760f9a456931466bf77dffe688f2a11f468aae5c074f74667957c6608 languageName: node linkType: hard @@ -12148,15 +12131,15 @@ __metadata: languageName: node linkType: hard -"metro-cache@npm:0.83.4": - version: 0.83.4 - resolution: "metro-cache@npm:0.83.4" +"metro-cache@npm:0.83.5": + version: 0.83.5 + resolution: "metro-cache@npm:0.83.5" dependencies: exponential-backoff: "npm:^3.1.1" flow-enums-runtime: "npm:^0.0.6" https-proxy-agent: "npm:^7.0.5" - metro-core: "npm:0.83.4" - checksum: 10/0835cfa70dc912e54217eda871aa3610c6731f36d6e8154cdaa148313a3b10b1fdebc1462e51dc94c146a438297f0b3f9627b76ebc19aad79641c669bb564575 + metro-core: "npm:0.83.5" + checksum: 10/f2b3b9e85e46f262b0adeb36dcbd2e14692199ba834757013bc7fca200f66573ca1d3925090597326764f4efe57da3a1416b8b611cf83b6c965541a3c51af4f2 languageName: node linkType: hard @@ -12192,19 +12175,19 @@ __metadata: languageName: node linkType: hard -"metro-config@npm:0.83.4, metro-config@npm:^0.83.1, metro-config@npm:^0.83.3": - version: 0.83.4 - resolution: "metro-config@npm:0.83.4" +"metro-config@npm:0.83.5, metro-config@npm:^0.83.1, metro-config@npm:^0.83.3": + version: 0.83.5 + resolution: "metro-config@npm:0.83.5" dependencies: connect: "npm:^3.6.5" flow-enums-runtime: "npm:^0.0.6" jest-validate: "npm:^29.7.0" - metro: "npm:0.83.4" - metro-cache: "npm:0.83.4" - metro-core: "npm:0.83.4" - metro-runtime: "npm:0.83.4" + metro: "npm:0.83.5" + metro-cache: "npm:0.83.5" + metro-core: "npm:0.83.5" + metro-runtime: "npm:0.83.5" yaml: "npm:^2.6.1" - checksum: 10/2056a0efb7c17ca7fc8bb8c157ff3fef1314ba53c92911aeed12eff362bd3eda3f670d69bdcab1d8fe466a81e750945a4f0cec05e7ab6961edad8f95fdede4c5 + checksum: 10/d085f7cd50b7c8557bd5b105fb23551ac3915ef162b62443fb9c44d9e25d450e37a729177c1267063167b5445e779c136b9a123c2c968d9ddfe6f979fb3f9ae2 languageName: node linkType: hard @@ -12230,14 +12213,14 @@ __metadata: languageName: node linkType: hard -"metro-core@npm:0.83.4, metro-core@npm:^0.83.1": - version: 0.83.4 - resolution: "metro-core@npm:0.83.4" +"metro-core@npm:0.83.5, metro-core@npm:^0.83.1": + version: 0.83.5 + resolution: "metro-core@npm:0.83.5" dependencies: flow-enums-runtime: "npm:^0.0.6" lodash.throttle: "npm:^4.1.1" - metro-resolver: "npm:0.83.4" - checksum: 10/de8c156aab7d029547ae72f707c3a4943138044b9ed28fb10461869d4eb5bcb8a099c3c4073f2f50ec34b33aca989ce6056ff876297b5c69a12b8c73effac8bb + metro-resolver: "npm:0.83.5" + checksum: 10/a65e83fc73f2cc42f9ea72f9d6c976b2272c9c3477f17c6a1288497995a5572d2a89c2ebf29b8ff45195bde29b2ae90fa58b7238dfcfe07928289f58049c2842 languageName: node linkType: hard @@ -12275,9 +12258,9 @@ __metadata: languageName: node linkType: hard -"metro-file-map@npm:0.83.4": - version: 0.83.4 - resolution: "metro-file-map@npm:0.83.4" +"metro-file-map@npm:0.83.5": + version: 0.83.5 + resolution: "metro-file-map@npm:0.83.5" dependencies: debug: "npm:^4.4.0" fb-watchman: "npm:^2.0.0" @@ -12288,7 +12271,7 @@ __metadata: micromatch: "npm:^4.0.4" nullthrows: "npm:^1.1.1" walker: "npm:^1.0.7" - checksum: 10/afb9f03aba8e0b2a97e3594aa88101a3fae360efdd003675dce07322f0d672f0f3256b71cf20dfffd556cc898fbf2d0eeba3555003e36922a988c50b0d62de41 + checksum: 10/0cce73c75bbf9b248628285554ddd73fce6f4e86ee4776c9f6b65fcf2cfd1f75b15e3f4cf2dc44ad91e5c78fc61a6eb7d3daaee09b61af2b55d82558a2b0423c languageName: node linkType: hard @@ -12312,13 +12295,13 @@ __metadata: languageName: node linkType: hard -"metro-minify-terser@npm:0.83.4": - version: 0.83.4 - resolution: "metro-minify-terser@npm:0.83.4" +"metro-minify-terser@npm:0.83.5": + version: 0.83.5 + resolution: "metro-minify-terser@npm:0.83.5" dependencies: flow-enums-runtime: "npm:^0.0.6" terser: "npm:^5.15.0" - checksum: 10/3e3c7da77e1c8b256f2fc4a4db2f0db6227ef094297d6c57d67fa9d89cfe559867fa733b4d0373112a9a7ae9255a435943fb9755e4e4dcaaacbf85e1c690c27d + checksum: 10/b9e257b5a74343a271e89603479775ed76b9c5e7b28015bafbce2afb4d7507acf36e897fc78c2ee571ad89951ba0ca708188ecb33fff0b947d1cee0ea8fd7837 languageName: node linkType: hard @@ -12389,12 +12372,12 @@ __metadata: languageName: node linkType: hard -"metro-resolver@npm:0.83.4": - version: 0.83.4 - resolution: "metro-resolver@npm:0.83.4" +"metro-resolver@npm:0.83.5": + version: 0.83.5 + resolution: "metro-resolver@npm:0.83.5" dependencies: flow-enums-runtime: "npm:^0.0.6" - checksum: 10/4b51bc061a712cc0f79172cdaeef7eae4c7e1b4a38468df4a7d1a7fc76bc6669b0abc9a98a41ed25102efe04502ec5eb65f9112ed0d6d809f7a05f723f15bebf + checksum: 10/0ad900735aa3446d8e5b341ff921b990895bb26517be96530b2a7c21504a617fa079299447b5ea4e3014894c94bcab7da54d37cbdc00bcc0c54f5c645c1d42cd languageName: node linkType: hard @@ -12418,13 +12401,13 @@ __metadata: languageName: node linkType: hard -"metro-runtime@npm:0.83.4, metro-runtime@npm:^0.83.1, metro-runtime@npm:^0.83.3": - version: 0.83.4 - resolution: "metro-runtime@npm:0.83.4" +"metro-runtime@npm:0.83.5, metro-runtime@npm:^0.83.1, metro-runtime@npm:^0.83.3": + version: 0.83.5 + resolution: "metro-runtime@npm:0.83.5" dependencies: "@babel/runtime": "npm:^7.25.0" flow-enums-runtime: "npm:^0.0.6" - checksum: 10/2f518a73bcdfea680499515e7dff5a5e7a69a3c0fdd980014d31acda8705d72456aa9f325ae250c782bceab4c37a139b1e5f417ad57b8a5b36dddb25dc8f5699 + checksum: 10/95a5f670fb2b230eea86e29833d0353c0fc845905fdae65c2f8a63c272ea095bf94976db7e28908bc6213ca22dffc21438eb18360321d92d8fb5aeb12a8d7520 languageName: node linkType: hard @@ -12464,20 +12447,20 @@ __metadata: languageName: node linkType: hard -"metro-source-map@npm:0.83.4, metro-source-map@npm:^0.83.1": - version: 0.83.4 - resolution: "metro-source-map@npm:0.83.4" +"metro-source-map@npm:0.83.5, metro-source-map@npm:^0.83.1": + version: 0.83.5 + resolution: "metro-source-map@npm:0.83.5" dependencies: "@babel/traverse": "npm:^7.29.0" "@babel/types": "npm:^7.29.0" flow-enums-runtime: "npm:^0.0.6" invariant: "npm:^2.2.4" - metro-symbolicate: "npm:0.83.4" + metro-symbolicate: "npm:0.83.5" nullthrows: "npm:^1.1.1" - ob1: "npm:0.83.4" + ob1: "npm:0.83.5" source-map: "npm:^0.5.6" vlq: "npm:^1.0.0" - checksum: 10/9e634269e8d20e335b4e31a1bdf94dd3537f33722d80994c6356ab7a976dfc44cfc8389637e116b218b30b7823d4b4ddfbd4cbbb363aead146a010c31110d484 + checksum: 10/55e9562f95e1056b48bd4b705a8ff01998c0bb9da2166638141ce7404f8800caa5c7ba077ead999809245400e38bbff1e175c2feefd044ac78a69f9a69c73d3d languageName: node linkType: hard @@ -12513,19 +12496,19 @@ __metadata: languageName: node linkType: hard -"metro-symbolicate@npm:0.83.4": - version: 0.83.4 - resolution: "metro-symbolicate@npm:0.83.4" +"metro-symbolicate@npm:0.83.5": + version: 0.83.5 + resolution: "metro-symbolicate@npm:0.83.5" dependencies: flow-enums-runtime: "npm:^0.0.6" invariant: "npm:^2.2.4" - metro-source-map: "npm:0.83.4" + metro-source-map: "npm:0.83.5" nullthrows: "npm:^1.1.1" source-map: "npm:^0.5.6" vlq: "npm:^1.0.0" bin: metro-symbolicate: src/index.js - checksum: 10/671b7046969a6878a6eea0199a753ed52f9159da6726eccf018a2723aae169e877caa63a83897f9206e288bbc40d7306c4331a2287b6412ab5f973434a1f1019 + checksum: 10/56cab184eff91d13f6122342f6564dd1b9bba97a32017c21ca1b0dade69a9020a53ef6971668a02ac0d4c457a05941162f3e6052a5854d124a30a63ee611d59b languageName: node linkType: hard @@ -12557,9 +12540,9 @@ __metadata: languageName: node linkType: hard -"metro-transform-plugins@npm:0.83.4": - version: 0.83.4 - resolution: "metro-transform-plugins@npm:0.83.4" +"metro-transform-plugins@npm:0.83.5": + version: 0.83.5 + resolution: "metro-transform-plugins@npm:0.83.5" dependencies: "@babel/core": "npm:^7.25.2" "@babel/generator": "npm:^7.29.1" @@ -12567,7 +12550,7 @@ __metadata: "@babel/traverse": "npm:^7.29.0" flow-enums-runtime: "npm:^0.0.6" nullthrows: "npm:^1.1.1" - checksum: 10/613a9c30bda3221b6fe113b0136c0047ab68c8e87fc0f38d7d7ee7c570a7f3e2866c923aaf9bf1230ed46918bfd1763898ff82225be1a765e7d2b8b0676594af + checksum: 10/227da814239803d8c8288a403fe166e4d99b4d070426c57dc4a02e82c117cf9398b40a82b5e1060f1ebdb65a882dab840dbbea7d3f09a97ef3d3e4f6297fc2af languageName: node linkType: hard @@ -12613,24 +12596,24 @@ __metadata: languageName: node linkType: hard -"metro-transform-worker@npm:0.83.4": - version: 0.83.4 - resolution: "metro-transform-worker@npm:0.83.4" +"metro-transform-worker@npm:0.83.5": + version: 0.83.5 + resolution: "metro-transform-worker@npm:0.83.5" dependencies: "@babel/core": "npm:^7.25.2" "@babel/generator": "npm:^7.29.1" "@babel/parser": "npm:^7.29.0" "@babel/types": "npm:^7.29.0" flow-enums-runtime: "npm:^0.0.6" - metro: "npm:0.83.4" - metro-babel-transformer: "npm:0.83.4" - metro-cache: "npm:0.83.4" - metro-cache-key: "npm:0.83.4" - metro-minify-terser: "npm:0.83.4" - metro-source-map: "npm:0.83.4" - metro-transform-plugins: "npm:0.83.4" + metro: "npm:0.83.5" + metro-babel-transformer: "npm:0.83.5" + metro-cache: "npm:0.83.5" + metro-cache-key: "npm:0.83.5" + metro-minify-terser: "npm:0.83.5" + metro-source-map: "npm:0.83.5" + metro-transform-plugins: "npm:0.83.5" nullthrows: "npm:^1.1.1" - checksum: 10/99269c498d95a68815d28e5d9e14eed25e4ac59014c1891c50bbcd10ee6700b69c796c375cba95669b448d2faa8786ca0a5237dd9c9c865f51b554510f95f775 + checksum: 10/6f3201cde7af9cb063ce0dd40b695dbcc658856e8db1d03d3b0c6854dab692477c33885c7891cb2f829ca6c682e7842f9a1801ac4c62db711183d2f7dd33a10d languageName: node linkType: hard @@ -12734,9 +12717,9 @@ __metadata: languageName: node linkType: hard -"metro@npm:0.83.4, metro@npm:^0.83.1": - version: 0.83.4 - resolution: "metro@npm:0.83.4" +"metro@npm:0.83.5, metro@npm:^0.83.1": + version: 0.83.5 + resolution: "metro@npm:0.83.5" dependencies: "@babel/code-frame": "npm:^7.29.0" "@babel/core": "npm:^7.25.2" @@ -12759,18 +12742,18 @@ __metadata: jest-worker: "npm:^29.7.0" jsc-safe-url: "npm:^0.2.2" lodash.throttle: "npm:^4.1.1" - metro-babel-transformer: "npm:0.83.4" - metro-cache: "npm:0.83.4" - metro-cache-key: "npm:0.83.4" - metro-config: "npm:0.83.4" - metro-core: "npm:0.83.4" - metro-file-map: "npm:0.83.4" - metro-resolver: "npm:0.83.4" - metro-runtime: "npm:0.83.4" - metro-source-map: "npm:0.83.4" - metro-symbolicate: "npm:0.83.4" - metro-transform-plugins: "npm:0.83.4" - metro-transform-worker: "npm:0.83.4" + metro-babel-transformer: "npm:0.83.5" + metro-cache: "npm:0.83.5" + metro-cache-key: "npm:0.83.5" + metro-config: "npm:0.83.5" + metro-core: "npm:0.83.5" + metro-file-map: "npm:0.83.5" + metro-resolver: "npm:0.83.5" + metro-runtime: "npm:0.83.5" + metro-source-map: "npm:0.83.5" + metro-symbolicate: "npm:0.83.5" + metro-transform-plugins: "npm:0.83.5" + metro-transform-worker: "npm:0.83.5" mime-types: "npm:^3.0.1" nullthrows: "npm:^1.1.1" serialize-error: "npm:^2.1.0" @@ -12780,7 +12763,7 @@ __metadata: yargs: "npm:^17.6.2" bin: metro: src/cli.js - checksum: 10/9986b40bca503d97c270e3821bd2786d51de1f8f4759d5fec9b8454ea8f46f6d5769324ad2c05df0faef9379831f17e1a1f7f07e81e7821610521e5d47d1c5e1 + checksum: 10/3c4643121335cf157696531829448b2c86ec653d5a7a11aa9cd005a1b9ad7a3f87f5e6ba8b997fc87e7b9f679a212d74db16739b4526a42425c6fb83e86283dc languageName: node linkType: hard @@ -13123,11 +13106,11 @@ __metadata: linkType: hard "minimatch@npm:^9.0.0, minimatch@npm:^9.0.4": - version: 9.0.8 - resolution: "minimatch@npm:9.0.8" + version: 9.0.9 + resolution: "minimatch@npm:9.0.9" dependencies: - brace-expansion: "npm:^5.0.2" - checksum: 10/bffa6514fa576a5052cea75b96b1d4086598c3ba45464dffcff14d00c2866805c088072ae21460ade250455c2da72e67556b1c3b830ddaf0b4697dca62f6d744 + brace-expansion: "npm:^2.0.2" + checksum: 10/b91fad937deaffb68a45a2cb731ff3cff1c3baf9b6469c879477ed16f15c8f4ce39d63a3f75c2455107c2fdff0f3ab597d97dc09e2e93b883aafcf926ef0c8f9 languageName: node linkType: hard @@ -13365,9 +13348,9 @@ __metadata: linkType: hard "node-releases@npm:^2.0.27": - version: 2.0.27 - resolution: "node-releases@npm:2.0.27" - checksum: 10/f6c78ddb392ae500719644afcbe68a9ea533242c02312eb6a34e8478506eb7482a3fb709c70235b01c32fe65625b68dfa9665113f816d87f163bc3819b62b106 + version: 2.0.36 + resolution: "node-releases@npm:2.0.36" + checksum: 10/b31ead96e328b1775f07cad80c17b0601d0ee2894650b737e7ab5cbeb14e284e82dbc37ef38f1d915fa46dd7909781bd933d19b79cfe31b352573fac6da377aa languageName: node linkType: hard @@ -13451,12 +13434,12 @@ __metadata: languageName: node linkType: hard -"ob1@npm:0.83.4": - version: 0.83.4 - resolution: "ob1@npm:0.83.4" +"ob1@npm:0.83.5": + version: 0.83.5 + resolution: "ob1@npm:0.83.5" dependencies: flow-enums-runtime: "npm:^0.0.6" - checksum: 10/418eb2c819c6af464b20525f309eb49c0ac69ae43c4476b147cf1d1db4103c0c77b79bd420f3ba382c608d60c969e7da23228c6bf16bf241a15cf7aaf3becd11 + checksum: 10/7a3ed43344d3d10c76060218fc35c652d12e20c0e520cf4bdb3c86c2817f0622b78a3d8c81fd52a05c29d7d2113b65514ee721e61adb352dd547d14a74b6015a languageName: node linkType: hard @@ -13819,6 +13802,13 @@ __metadata: languageName: node linkType: hard +"path-expression-matcher@npm:^1.1.3": + version: 1.1.3 + resolution: "path-expression-matcher@npm:1.1.3" + checksum: 10/9a607d0bf9807cf86b0a29fb4263f0c00285c13bedafb6ad3efc8bc87ae878da2faf657a9138ac918726cb19f147235a0ca695aec3e4ea1ee04641b6520e6c9e + languageName: node + linkType: hard + "path-is-absolute@npm:^1.0.0": version: 1.0.1 resolution: "path-is-absolute@npm:1.0.1" @@ -14008,14 +13998,14 @@ __metadata: languageName: node linkType: hard -"pretty-format@npm:30.2.0, pretty-format@npm:^30.0.0": - version: 30.2.0 - resolution: "pretty-format@npm:30.2.0" +"pretty-format@npm:30.3.0, pretty-format@npm:^30.0.0": + version: 30.3.0 + resolution: "pretty-format@npm:30.3.0" dependencies: "@jest/schemas": "npm:30.0.5" ansi-styles: "npm:^5.2.0" react-is: "npm:^18.3.1" - checksum: 10/725890d648e3400575eebc99a334a4cd1498e0d36746313913706bbeea20ada27e17c184a3cd45c50f705c16111afa829f3450233fc0fda5eed293c69757e926 + checksum: 10/b288db630841f2464554c5cfa7d7faf519ad7b5c05c3818e764c7cb486bcf59f240ea5576c748f8ca6625623c5856a8906642255bbe89d6cfa1a9090b0fbc6b9 languageName: node linkType: hard @@ -14082,12 +14072,12 @@ __metadata: linkType: hard "pump@npm:^3.0.0": - version: 3.0.3 - resolution: "pump@npm:3.0.3" + version: 3.0.4 + resolution: "pump@npm:3.0.4" dependencies: end-of-stream: "npm:^1.1.0" once: "npm:^1.3.1" - checksum: 10/52843fc933b838c0330f588388115a1b28ef2a5ffa7774709b142e35431e8ab0c2edec90de3fa34ebb72d59fef854f151eea7dfc211b6dcf586b384556bd2f39 + checksum: 10/d043c3e710c56ffd280711e98a94e863ab334f79ea43cee0fb70e1349b2355ffd2ff287c7522e4c960a247699d5b7825f00fa090b85d6179c973be13f78a6c49 languageName: node linkType: hard @@ -14430,7 +14420,7 @@ __metadata: languageName: node linkType: hard -"react-native-is-edge-to-edge@npm:1.2.1, react-native-is-edge-to-edge@npm:^1.1.6, react-native-is-edge-to-edge@npm:^1.2.1": +"react-native-is-edge-to-edge@npm:1.2.1": version: 1.2.1 resolution: "react-native-is-edge-to-edge@npm:1.2.1" peerDependencies: @@ -14440,6 +14430,16 @@ __metadata: languageName: node linkType: hard +"react-native-is-edge-to-edge@npm:^1.1.6, react-native-is-edge-to-edge@npm:^1.2.1": + version: 1.3.1 + resolution: "react-native-is-edge-to-edge@npm:1.3.1" + peerDependencies: + react: "*" + react-native: "*" + checksum: 10/dc82d54e0bf8f89208a538bb0d14e4891af6efae27ed5b7b21be683a72c38c5219ab9be1ea9bd40aa1c905d481174e649d0b71aeceaa9946e6c707f251568282 + languageName: node + linkType: hard + "react-native-loading-spinner-overlay@npm:^3.0.1": version: 3.0.1 resolution: "react-native-loading-spinner-overlay@npm:3.0.1" @@ -15119,10 +15119,10 @@ __metadata: languageName: node linkType: hard -"sax@npm:>=0.6.0": - version: 1.4.4 - resolution: "sax@npm:1.4.4" - checksum: 10/00ff7b258baa37d98f8abfa0b5c8b3ee739ca37e9b6ecb83405be9e6e5b0b2856394a5eff142db1d987d589b54b139d4236f25830c1e17a2b640efa53c8fda72 +"sax@npm:>=0.6.0, sax@npm:^1.5.0": + version: 1.5.0 + resolution: "sax@npm:1.5.0" + checksum: 10/9012ff37dda7a7ac5da45db2143b04036103e8bef8d586c3023afd5df6caf0ebd7f38017eee344ad2e2247eded7d38e9c42cf291d8dd91781352900ac0fd2d9f languageName: node linkType: hard @@ -15428,9 +15428,9 @@ __metadata: linkType: hard "slugify@npm:^1.3.4, slugify@npm:^1.6.6": - version: 1.6.6 - resolution: "slugify@npm:1.6.6" - checksum: 10/d0737cdedc834c50f74227bc1a1cf4f449f3575893f031b0e8c59f501c73526c866a23e47261b262c7acdaaaaf30d6f9e8aaae22772b3f56e858ac84c35efa7b + version: 1.6.8 + resolution: "slugify@npm:1.6.8" + checksum: 10/d80c3ce9e57d67ba60f4fc37ac4e2852174eab6402d16daeb6896ced1da838ef650a563a0069266198a866407744334fcd8c02b3470e0a9be348b885cf5db8a7 languageName: node linkType: hard @@ -15812,9 +15812,9 @@ __metadata: linkType: hard "strnum@npm:^2.1.2": - version: 2.1.2 - resolution: "strnum@npm:2.1.2" - checksum: 10/7d894dff385e3a5c5b29c012cf0a7ea7962a92c6a299383c3d6db945ad2b6f3e770511356a9774dbd54444c56af1dc7c435dad6466c47293c48173274dd6c631 + version: 2.2.0 + resolution: "strnum@npm:2.2.0" + checksum: 10/2969dbc8441f5af1b55db1d2fcea64a8f912de18515b57f85574e66bdb8f30ae76c419cf1390b343d72d687e2aea5aca82390f18b9e0de45d6bcc6d605eb9385 languageName: node linkType: hard @@ -15895,19 +15895,19 @@ __metadata: linkType: hard "svgo@npm:^3.0.2": - version: 3.3.2 - resolution: "svgo@npm:3.3.2" + version: 3.3.3 + resolution: "svgo@npm:3.3.3" dependencies: - "@trysound/sax": "npm:0.2.0" commander: "npm:^7.2.0" css-select: "npm:^5.1.0" css-tree: "npm:^2.3.1" css-what: "npm:^6.1.0" csso: "npm:^5.0.5" picocolors: "npm:^1.0.0" + sax: "npm:^1.5.0" bin: svgo: ./bin/svgo - checksum: 10/82fdea9b938884d808506104228e4d3af0050d643d5b46ff7abc903ff47a91bbf6561373394868aaf07a28f006c4057b8fbf14bbd666298abdd7cc590d4f7700 + checksum: 10/f3c1b4d05d1704483e53515d5995af5f06a2718df85e3a8320f57bb256b8dc926b84c87a1a9b98e9d3ca1224314cc0676a803bdd03163508292f2d45c7077096 languageName: node linkType: hard @@ -15921,15 +15921,15 @@ __metadata: linkType: hard "tar@npm:^7.5.2, tar@npm:^7.5.4": - version: 7.5.9 - resolution: "tar@npm:7.5.9" + version: 7.5.11 + resolution: "tar@npm:7.5.11" dependencies: "@isaacs/fs-minipass": "npm:^4.0.0" chownr: "npm:^3.0.0" minipass: "npm:^7.1.2" minizlib: "npm:^3.1.0" yallist: "npm:^5.0.0" - checksum: 10/1213cdde9c22d6acf8809ba5d2a025212ce3517bc99c4a4c6981b7dc0489bf3b164db9c826c9517680889194c9ba57448c8ff0da35eca9a60bb7689bf0b3897d + checksum: 10/fb2e77ee858a73936c68e066f4a602d428d6f812e6da0cc1e14a41f99498e4f7fd3535e355fa15157240a5538aa416026cfa6306bb0d1d1c1abf314b1f878e9a languageName: node linkType: hard @@ -15944,8 +15944,8 @@ __metadata: linkType: hard "terser@npm:^5.15.0": - version: 5.46.0 - resolution: "terser@npm:5.46.0" + version: 5.46.1 + resolution: "terser@npm:5.46.1" dependencies: "@jridgewell/source-map": "npm:^0.3.3" acorn: "npm:^8.15.0" @@ -15953,7 +15953,7 @@ __metadata: source-map-support: "npm:~0.5.20" bin: terser: bin/terser - checksum: 10/331e4f5a165d91d16ac6a95b510d4f5ef24679e4bc9e1b4e4182e89b7245f614d24ce0def583e2ca3ca45f82ba810991e0c5b66dd4353a6e0b7082786af6bd35 + checksum: 10/16d21179905e549dae2560e107d069ba0fdb801c637bf5f07c2f30431e53b1641151d5e35915ca6578ac1d9763984095723034bf1a26740b183093f200293f86 languageName: node linkType: hard @@ -16266,9 +16266,9 @@ __metadata: linkType: hard "undici@npm:^6.18.2": - version: 6.23.0 - resolution: "undici@npm:6.23.0" - checksum: 10/56950995e7b628e62c996430445d17995ca9b70f6f2afe760a63da54205660d968bd08f0741b6f4fb008f40aa35c69cce979cd96ced399585d8c897a76a4f1d1 + version: 6.24.1 + resolution: "undici@npm:6.24.1" + checksum: 10/4f84e6045520eef9ba8eabb96360b50c759f59905c1703b12187c2dbcc6d1584c5d7ecddeb45b0ed6cac84ca2d132b21bfd8a38f77fa30378b1ac5d2ae390fd9 languageName: node linkType: hard