Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

add 16 bit png support #5413

Merged
merged 5 commits into from
Jan 22, 2025
Merged

add 16 bit png support #5413

merged 5 commits into from
Jan 22, 2025

Conversation

sashankaryal
Copy link
Contributor

@sashankaryal sashankaryal commented Jan 20, 2025

addresses #5363

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced PNG image decoding support with improved handling of 16-bit images
    • Added advanced mask decoding functionality for different image formats
  • Bug Fixes

    • Improved error handling for image decoding processes
    • Simplified image channel determination logic
  • Performance

    • Streamlined image processing methods
    • Optimized overlay mask decoding mechanisms
  • Technical Improvements

    • Removed deprecated PNG color type detection
    • Updated decoding strategies for more robust image processing

Copy link
Contributor

coderabbitai bot commented Jan 20, 2025

Warning

Rate limit exceeded

@sashankaryal has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 20 minutes and 35 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 2aeed5a and 644a400.

📒 Files selected for processing (1)
  • app/packages/looker/src/worker/mask-decoder.ts (1 hunks)

Walkthrough

This pull request introduces significant modifications to image decoding and processing in the FiftyOne project. The changes primarily focus on simplifying PNG image handling, introducing a new customDecode16BitPng function, and refactoring the overlay decoding mechanism. The modifications streamline the image decoding process by removing complex PNG color type detection and replacing it with a more direct approach that relies on external channel information.

Changes

File Change Summary
app/packages/looker/src/worker/canvas-decoder.ts Removed getPngcolorType function, updated decodeWithCanvas method signature to include numOriginalChannels parameter
app/packages/looker/src/worker/custom-16-bit-png-decoder.ts Added new customDecode16BitPng function for processing 16-bit PNG images
app/packages/looker/src/worker/disk-overlay-decoder.ts Updated import statements, replaced decodeWithCanvas with decodeMaskOnDisk
app/packages/looker/src/worker/mask-decoder.ts Added getMaybePngHeader function, modified decodeMaskOnDisk to handle different PNG formats
app/packages/looker/src/worker/mask-decoder.test.ts Added comprehensive unit tests for decodeMaskOnDisk function
app/packages/looker/src/worker/disk-overlay-decoder.test.ts Updated test mocks to use decodeMaskOnDisk instead of decodeWithCanvas

Sequence Diagram

sequenceDiagram
    participant Caller
    participant MaskDecoder
    participant CustomPngDecoder
    participant CanvasDecoder

    Caller->>MaskDecoder: decodeMaskOnDisk(blob, cls)
    MaskDecoder->>MaskDecoder: Check image type
    alt JPEG/JPG
        MaskDecoder->>CanvasDecoder: decodeWithCanvas
    else PNG
        MaskDecoder->>MaskDecoder: Get PNG header
        alt 16-bit PNG
            MaskDecoder->>CustomPngDecoder: customDecode16BitPng
        else Other PNG
            MaskDecoder->>CanvasDecoder: decodeWithCanvas
        end
    end
Loading

Possibly related PRs

Suggested labels

app, bug

Suggested reviewers

  • benjaminpkane
  • ritch

Poem

🐰 In bytes of PNG, a rabbit's delight,
Decoding masks with algorithmic might
Channels simplified, complexity shed
No more color types dancing in my head
A cleaner path through image's domain! 🖼️


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@sashankaryal sashankaryal changed the base branch from develop to release/v1.3.0 January 20, 2025 20:11
@sashankaryal sashankaryal requested a review from a team January 20, 2025 20:11
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments.

Actionable comments posted: 1

🧹 Nitpick comments (7)
fiftyone/utils/eval/base.py (1)

Line range hint 219-283: Documentation could be more explicit about default behavior

The implementation looks good, but consider making the docstring more explicit about what "only include a row/column for missing labels if there are any" means in practice.

Add this clarification to the docstring:

             include_missing (None): whether to include a row/column for missing
                 ground truth/predictions in the confusion matrix. The supported
                 values are:

                 -   None (default): only include a row/column for missing
-                    labels if there are any
+                    labels if there are any unmatched ground truth or predicted
+                    labels in the evaluation results
                 -   True: do include a row/column for missing labels
                 -   False: do not include a row/column for missing labels
app/packages/operators/src/components/OperatorExecutionTrigger/index.tsx (1)

76-79: Consider a more robust options merging strategy.

The current spread operator usage (...executorOptions, ...options) might accidentally override important options. Consider implementing a deep merge or explicitly defining which options can be overridden.

-      const resolvedOptions = {
-        ...executorOptions,
-        ...options,
-      };
+      const resolvedOptions = {
+        ...executorOptions,
+        ...(options ?? {}),
+        // Explicitly list options that should be overrideable
+        target: options?.target ?? executorOptions?.target,
+        // Add other specific options here
+      };
app/packages/looker/src/worker/custom-16-bit-png-decoder.ts (1)

16-17: Document channel calculation fallback

The fallback calculation for channels should be documented to explain why and when it's used.

   const numChannels =
-    decodedPng.channels ?? decodedPng.data.length / (width * height);
+    // If channels is not specified, calculate from data length
+    // For 16-bit PNGs: 1 channel = grayscale, 2 = gray+alpha, 3 = RGB, 4 = RGBA
+    decodedPng.channels ?? decodedPng.data.length / (width * height);
app/packages/operators/src/usePanelEvent.ts (1)

Line range hint 37-52: Improve error handling in eventCallback

The error handling could be more robust:

  1. The error message concatenation might result in undefined when no operator is provided
  2. The error logging could include more context
   const eventCallback = (result, opts) => {
     decrement(panelId);
-    const msg =
-      result.errorMessage || result.error || "Failed to execute operation";
-    const computedMsg = `${msg} (operation: ${operator})`;
+    const msg = result.errorMessage || result.error || "Failed to execute operation";
+    const computedMsg = `${msg} (operation: ${operator || "unknown"})`;
     if (result?.error) {
       notify({ msg: computedMsg, variant: "error" });
-      console.error(result?.error);
+      console.error("Operation failed:", {
+        error: result.error,
+        operator,
+        params: actualParams
+      });
     }
     options?.callback?.(result, opts);
   };
app/packages/core/src/components/Modal/ModalLooker.tsx (1)

68-71: Enhance key prop usage for consistency

Consider:

  1. Making the key more specific to avoid potential conflicts
  2. Adding the same key to VideoLookerReact for consistent behavior
-    const modalMediaField = useRecoilValue(fos.selectedMediaField(true));
+    const modalMediaField = useRecoilValue(fos.selectedMediaField(true));
+    const componentKey = `modal-${modalMediaField}`;

     if (shouldRenderImavid) {
-      return <ImaVidLookerReact sample={sample} key={modalMediaField} />;
+      return <ImaVidLookerReact sample={sample} key={componentKey} />;
     }

     if (video) {
-      return <VideoLookerReact sample={sample} />;
+      return <VideoLookerReact sample={sample} key={componentKey} />;
     }
app/packages/state/src/hooks/useCreateLooker.ts (1)

227-230: Improve key composition for better maintainability.

The composite key creation can be moved to a utility function for better maintainability and reuse. This would also help ensure consistent key generation across the codebase.

Consider applying this change:

-const imavidPartitionKey = `${thisSampleId}-${mediaField}`;
+const createImavidPartitionKey = (sampleId: string, mediaField: string) => `${sampleId}-${mediaField}`;
+const imavidPartitionKey = createImavidPartitionKey(thisSampleId, mediaField);
app/packages/operators/src/built-in-operators.ts (1)

Line range hint 1048-1074: LGTM! The callback handling has been simplified.

The removal of the on_cancel callback and the refactoring of the success/error handling logic makes the code cleaner and more maintainable.

Consider adding TypeScript type annotations for the params object to improve type safety:

-    const { params, operator_uri, on_success, on_error } = ctx.params;
+    const { 
+      params,
+      operator_uri,
+      on_success,
+      on_error 
+    }: {
+      params: Record<string, unknown>;
+      operator_uri: string;
+      on_success?: string;
+      on_error?: string;
+    } = ctx.params;
🛑 Comments failed to post (1)
app/packages/core/src/components/Modal/ImaVidLooker.tsx (1)

198-198: ⚠️ Potential issue

Potential memory leak: Event listener cleanup needed.

Removing { once: true } means the event listener will persist. This could lead to memory leaks if the event listeners accumulate over time. Either:

  1. Re-add { once: true }, or
  2. Ensure proper cleanup in component unmount

Apply one of these changes:

# Option 1: Re-add once: true
-fetchMoreListener as EventListener
+fetchMoreListener as EventListener,
+{ once: true }

# Option 2: Add cleanup
+useEffect(() => {
+  return () => {
+    window.removeEventListener("fetchMore", fetchMoreListener as EventListener);
+  };
+}, []);

Committable suggestion skipped: line range outside the PR's diff.

Copy link
Contributor

@benjaminpkane benjaminpkane left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a regression test is worthwhile for this. A vitest test case that asserts 4 x 4 16 bit png result maybe?

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (7)
app/packages/looker/src/worker/disk-overlay-decoder.ts (1)

Line range hint 109-115: Improve error handling during mask decoding

The try...catch block currently logs the error but does not provide context. Enhance the error message for better debugging and consider rethrowing the error if appropriate.

Apply this diff to improve error handling:

     try {
       overlayMask = await decodeMaskOnDisk(overlayImageBlob, cls);
     } catch (e) {
-      console.error(e);
+      console.error(`Error decoding overlay mask for class ${cls}:`, e);
       return;
     }
app/packages/looker/src/worker/mask-decoder.test.ts (4)

6-16: Use more dynamic mock implementations for better test coverage

The current mock for decodeWithCanvas returns fixed values. Consider making the mock return values based on input parameters to simulate different scenarios and enhance test reliability.

Example modification:

 vi.mock("./canvas-decoder", () => {
   return {
     decodeWithCanvas: vi.fn().mockImplementation(async (blob, cls, channels) => ({
       // Return dynamic fake decoded info based on input
       buffer: new ArrayBuffer(channels * 4),
       channels,
       arrayType: "Uint8Array",
       shape: [2, 2],
     })),
   };
 });

51-66: Consider using standard testing utilities for Blob mocking

Manually mocking Blob methods increases code complexity and maintenance burden. Utilize standard testing libraries like blob-polyfill or utilities from the testing framework to create Blob objects, improving readability and reliability.


90-91: Remove unused import of decodeWithCanvas

The import of decodeWithCanvas is redundant in the test file as it's being mocked and not directly used. This can help clean up the code.

Apply this diff to remove the unused import:

-import { decodeWithCanvas } from "./canvas-decoder";

122-139: Expand test cases to cover additional PNG formats

Currently, tests cover 16-bit grayscale and 8-bit truecolor PNGs. To ensure comprehensive coverage, add test cases for other color types, such as indexed-color (colorType 3) and grayscale with alpha (colorType 4), to verify the decoder's behavior across various formats.

app/packages/looker/src/worker/canvas-decoder.ts (2)

41-45: Add TypeScript validation and documentation for numOriginalChannels parameter.

The new parameter lacks type constraints and documentation. Consider adding:

  • Type validation to ensure valid channel counts (1-4)
  • JSDoc documentation explaining the parameter's purpose and valid values
+/**
+ * Decodes an image blob using canvas operations
+ * @param blob - The image blob to decode
+ * @param cls - The class type of the image
+ * @param numOriginalChannels - Number of channels in the original image (1-4)
+ */
 export const decodeWithCanvas = async (
   blob: Blob,
   cls: string,
-  numOriginalChannels: number
+  numOriginalChannels: 1 | 2 | 3 | 4
 ) => {

Line range hint 48-57: Ensure proper cleanup of resources.

The ImageBitmap is properly closed, but consider wrapping the canvas operations in a try-finally block to ensure cleanup in case of errors.

-  const imageBitmap = await createImageBitmap(blob);
-  const { width, height } = imageBitmap;
-
-  const { canvas: offScreenCanvas, ctx: offScreenCanvasCtx } = canvasAndCtx!;
-
-  offScreenCanvas.width = width;
-  offScreenCanvas.height = height;
-
-  offScreenCanvasCtx.drawImage(imageBitmap, 0, 0);
-
-  imageBitmap.close();
+  let imageBitmap;
+  try {
+    imageBitmap = await createImageBitmap(blob);
+    const { width, height } = imageBitmap;
+
+    const { canvas: offScreenCanvas, ctx: offScreenCanvasCtx } = canvasAndCtx!;
+
+    offScreenCanvas.width = width;
+    offScreenCanvas.height = height;
+
+    offScreenCanvasCtx.drawImage(imageBitmap, 0, 0);
+  } finally {
+    imageBitmap?.close();
+  }
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 47ac58a and 21af42d.

📒 Files selected for processing (4)
  • app/packages/looker/src/worker/canvas-decoder.ts (1 hunks)
  • app/packages/looker/src/worker/disk-overlay-decoder.ts (2 hunks)
  • app/packages/looker/src/worker/mask-decoder.test.ts (1 hunks)
  • app/packages/looker/src/worker/mask-decoder.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
app/packages/looker/src/worker/mask-decoder.test.ts (1)

Pattern **/*.{ts,tsx}: Review the Typescript and React code for conformity with best practices in React, Recoil, Graphql, and Typescript. Highlight any deviations.

app/packages/looker/src/worker/mask-decoder.ts (1)

Pattern **/*.{ts,tsx}: Review the Typescript and React code for conformity with best practices in React, Recoil, Graphql, and Typescript. Highlight any deviations.

app/packages/looker/src/worker/canvas-decoder.ts (1)

Pattern **/*.{ts,tsx}: Review the Typescript and React code for conformity with best practices in React, Recoil, Graphql, and Typescript. Highlight any deviations.

app/packages/looker/src/worker/disk-overlay-decoder.ts (1)

Pattern **/*.{ts,tsx}: Review the Typescript and React code for conformity with best practices in React, Recoil, Graphql, and Typescript. Highlight any deviations.

⏰ Context from checks skipped due to timeout of 90000ms (7)
  • GitHub Check: lint / eslint
  • GitHub Check: build / build
  • GitHub Check: test / test-python (ubuntu-latest-m, 3.11)
  • GitHub Check: test / test-python (ubuntu-latest-m, 3.10)
  • GitHub Check: test / test-app
  • GitHub Check: test / test-python (ubuntu-latest-m, 3.9)
  • GitHub Check: e2e / test-e2e
🔇 Additional comments (2)
app/packages/looker/src/worker/mask-decoder.ts (1)

17-31: Verify the byte offsets for bitDepth and colorType

The offsets used to access bitDepth and colorType in the header may be incorrect due to zero-based indexing in arrays. Confirm that the indices 24 and 25 correctly correspond to the bitDepth and colorType bytes in the PNG header.

To ensure accurate parsing, double-check the offsets against the PNG specification.

app/packages/looker/src/worker/canvas-decoder.ts (1)

Line range hint 59-83: Optimize buffer reallocation for performance.

The current implementation might perform unnecessary buffer reallocations. Consider:

  1. Pre-allocating the buffer with the correct size
  2. Adding early returns for optimization

Also, verify that this change properly handles 16-bit PNGs as intended by the PR.

✅ Verification successful

Current implementation is already well-optimized

The code already implements efficient buffer handling:

  • Buffers are pre-allocated with correct sizes
  • Reallocation only occurs when format conversion is necessary (grayscale or heatmap)
  • Proper support for different PNG bit depths (1, 2, 4, 8) is implemented in the indexed-png-decoder
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for tests or examples using 16-bit PNGs
rg -l "\.png" --glob "**/*test*" | while read -r file; do
  echo "=== $file ==="
  rg "16.*bit|uint16" "$file"
done

Length of output: 3702


Script:

#!/bin/bash
# Find recastBufferToMonoChannel implementation and PNG-related tests
echo "=== recastBufferToMonoChannel implementation ==="
ast-grep --pattern 'function recastBufferToMonoChannel'

echo -e "\n=== PNG-related tests in looker package ==="
fd -e test.ts -e spec.ts . app/packages/looker | while read -r file; do
  echo "=== $file ==="
  rg -A 5 "\.png|ImageData|canvas" "$file"
done

echo -e "\n=== Buffer allocation patterns ==="
rg -A 3 "new (Uint8Array|Uint16Array|Float32Array|Float64Array)" app/packages/looker

Length of output: 13348

app/packages/looker/src/worker/mask-decoder.ts Outdated Show resolved Hide resolved
app/packages/looker/src/worker/mask-decoder.ts Outdated Show resolved Hide resolved
app/packages/looker/src/worker/disk-overlay-decoder.ts Outdated Show resolved Hide resolved
@sashankaryal sashankaryal requested a review from a team as a code owner January 21, 2025 21:28
@sashankaryal sashankaryal changed the base branch from release/v1.3.0 to develop January 21, 2025 21:29
@sashankaryal sashankaryal removed the request for review from a team January 21, 2025 21:29
@sashankaryal
Copy link
Contributor Author

I think a regression test is worthwhile for this. A vitest test case that asserts 4 x 4 16 bit png result maybe?

good suggestion. done

@sashankaryal sashankaryal changed the title fix 16 bit png support add 16 bit png support Jan 21, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
app/packages/looker/src/worker/canvas-decoder.ts (1)

Line range hint 41-69: Consider adding TypeDoc comments for the updated function signature

The function's signature has changed significantly, but the documentation hasn't been updated to reflect the new parameter and its requirements.

+/**
+ * Decodes an image blob using canvas.
+ * @param blob - The image blob to decode
+ * @param cls - The class type of the image
+ * @param numOriginalChannels - Number of channels in the original image (1-4)
+ * @returns Promise<OverlayMask> - The decoded image data
+ * @throws {Error} When numOriginalChannels is invalid
+ */
export const decodeWithCanvas = async (
app/packages/looker/src/worker/mask-decoder.ts (2)

38-44: Enhance error handling for PNG header parsing

While the code handles invalid PNG signatures, it could benefit from more detailed error logging.

  if (blob.type === "image/png") {
    const headerInfo = await getPngHeader(blob);

    if (!headerInfo) {
-     console.warn("blob type is image/png but not a PNG file");
+     console.warn(
+       "Invalid PNG: blob has image/png MIME type but invalid PNG signature",
+       { blobSize: blob.size }
+     );
      return decodeWithCanvas(blob, cls, channels);
    }

54-59: Consider using an enum for PNG color types

The color type values and their meanings should be defined as an enum for better maintainability and type safety.

+export enum PngColorType {
+  Grayscale = 0,
+  Truecolor = 2,
+  IndexedColor = 3,
+  GrayscaleAlpha = 4,
+  RGBA = 6,
+}

// according to PNG specs:
// 0: Grayscale          => 1 channel
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 21af42d and 968d719.

⛔ Files ignored due to path filters (2)
  • app/packages/looker/package.json is excluded by !**/*.json
  • app/yarn.lock is excluded by !**/yarn.lock, !**/*.lock, !**/*.lock
📒 Files selected for processing (5)
  • app/packages/looker/src/worker/canvas-decoder.ts (1 hunks)
  • app/packages/looker/src/worker/custom-16-bit-png-decoder.ts (1 hunks)
  • app/packages/looker/src/worker/disk-overlay-decoder.ts (2 hunks)
  • app/packages/looker/src/worker/mask-decoder.test.ts (1 hunks)
  • app/packages/looker/src/worker/mask-decoder.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • app/packages/looker/src/worker/custom-16-bit-png-decoder.ts
  • app/packages/looker/src/worker/mask-decoder.test.ts
  • app/packages/looker/src/worker/disk-overlay-decoder.ts
🧰 Additional context used
📓 Path-based instructions (2)
app/packages/looker/src/worker/mask-decoder.ts (1)

Pattern **/*.{ts,tsx}: Review the Typescript and React code for conformity with best practices in React, Recoil, Graphql, and Typescript. Highlight any deviations.

app/packages/looker/src/worker/canvas-decoder.ts (1)

Pattern **/*.{ts,tsx}: Review the Typescript and React code for conformity with best practices in React, Recoil, Graphql, and Typescript. Highlight any deviations.

🔇 Additional comments (1)
app/packages/looker/src/worker/canvas-decoder.ts (1)

41-46: Add input validation for numOriginalChannels parameter

The function should validate the input parameter to ensure robust error handling.

export const decodeWithCanvas = async (
  blob: Blob,
  cls: string,
  numOriginalChannels: number
) => {
+  if (numOriginalChannels < 1 || numOriginalChannels > 4) {
+    throw new Error(`Invalid number of channels: ${numOriginalChannels}. Expected 1-4.`);
+  }
  let channels: number = numOriginalChannels;

app/packages/looker/src/worker/mask-decoder.ts Outdated Show resolved Hide resolved
This was referenced Jan 22, 2025
@sashankaryal sashankaryal changed the base branch from develop to release/v1.3.0 January 22, 2025 17:45
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments.

Actionable comments posted: 2

🔭 Outside diff range comments (1)
app/packages/looker/src/worker/canvas-decoder.ts (1)

Line range hint 48-52: Add error handling for canvas operations.

The canvas operations (createImageBitmap, drawImage, getImageData) could fail and should be wrapped in try-catch blocks.

-  const imageBitmap = await createImageBitmap(blob);
-  const { width, height } = imageBitmap;
-
-  const { canvas: offScreenCanvas, ctx: offScreenCanvasCtx } = canvasAndCtx!;
+  try {
+    const imageBitmap = await createImageBitmap(blob);
+    const { width, height } = imageBitmap;
+
+    const { canvas: offScreenCanvas, ctx: offScreenCanvasCtx } = canvasAndCtx!;
🧹 Nitpick comments (19)
app/packages/state/src/hooks/useCreateLooker.ts (1)

228-230: Consider adding error handling for store operations.

While the store operations are correctly implemented, consider adding error handling for edge cases where store operations might fail.

 if (!ImaVidFramesControllerStore.has(imavidPartitionKey)) {
+  try {
     ImaVidFramesControllerStore.set(
       imavidPartitionKey,
       new ImaVidFramesController({
         environment,
         firstFrameNumber,
         page,
         targetFrameRate: dynamicGroupsTargetFrameRateValue,
         totalFrameCountPromise,
         key: imavidKey,
       })
     );
+  } catch (error) {
+    console.error(`Failed to create controller for ${imavidPartitionKey}:`, error);
+    throw new Error(`Failed to initialize frame controller: ${error.message}`);
+  }
 }
app/packages/operators/src/components/OperatorExecutionButton/index.tsx (1)

Line range hint 10-23: Documentation could be enhanced with more detailed prop descriptions.

Consider adding more detailed JSDoc comments for each prop, especially for executionParams and onOptionSelected, to better explain their purpose and expected values.

app/packages/operators/src/usePanelEvent.ts (2)

Line range hint 26-33: Error handling could be more specific.

The generic error message could be enhanced to provide more context about why the operator was not provided. Consider adding error codes or more specific error messages.

-      notify({
-        msg: "No operator provided for panel event.",
-        variant: "error",
-      });
-      return console.error("No operator provided for panel event.");
+      const errorMsg = "No operator provided for panel event. Please ensure an operator is specified in the panel configuration.";
+      notify({
+        msg: errorMsg,
+        variant: "error",
+        code: "OPERATOR_MISSING"
+      });
+      return console.error(errorMsg);

Line range hint 8-13: Consider strengthening type safety for HandlerOptions.

The any type for currentPanelState could be replaced with a more specific type to improve type safety.

-  currentPanelState?: any; // most current panel state
+  currentPanelState?: Record<string, unknown>; // most current panel state
app/packages/operators/src/components/OperatorExecutionTrigger/index.tsx (1)

74-84: Consider dependency array optimization.

The useCallback dependency array could be optimized by destructuring executionParams earlier.

+  const params = executionParams ?? {};
   const onExecute = useCallback(
     (options?: OperatorExecutorOptions) => {
       const resolvedOptions = {
         ...executorOptions,
         ...options,
       };

-      return operator.execute(executionParams ?? {}, resolvedOptions);
+      return operator.execute(params, resolvedOptions);
     },
-    [executorOptions, operator, executionParams]
+    [executorOptions, operator, params]
   );
app/packages/core/src/plugins/SchemaIO/components/OperatorExecutionButtonView.tsx (2)

Line range hint 22-107: Consider breaking down the component for better maintainability.

The component is handling too many responsibilities. Consider extracting the event handlers and style logic into separate custom hooks or components.

Example structure:

// useOperatorEventHandlers.ts
export function useOperatorEventHandlers(props) {
  const triggerEvent = usePanelEvent();
  const panelId = usePanelId();
  
  return {
    handleOnSuccess: useCallback(...),
    handleOnError: useCallback(...),
    handleOnOptionSelected: useCallback(...)
  };
}

// OperatorExecutionButtonView.tsx
export default function OperatorExecutionButtonView(props: ViewPropsType) {
  const eventHandlers = useOperatorEventHandlers(props);
  // ... rest of the component
}

Line range hint 111-196: Move style-related functions to a separate file.

The style utility functions (getButtonProps, getIconProps, getCommonProps, getColor) should be moved to a separate file to improve code organization and reusability.

Create a new file buttonStyles.ts:

export function getButtonProps(props: ViewPropsType): ButtonProps {
  // ... existing implementation
}

export function getIconProps(props: ViewPropsType): ButtonProps {
  // ... existing implementation
}

// ... other style-related functions
app/packages/core/src/plugins/SchemaIO/index.tsx (1)

23-30: LGTM! Consider adding type annotations.

The changes improve value handling by ensuring consistent coercion before state updates. The implementation correctly handles state updates and propagates the coerced value to callbacks.

Consider adding TypeScript type annotations for better type safety:

-      const computedValue = coerceValue(value, schema);
+      const computedValue: unknown = coerceValue(value, schema);
app/packages/core/src/plugins/SchemaIO/components/DynamicIO.tsx (1)

39-44: LGTM! Consider improving readability.

The changes improve schema handling by providing a more robust fallback mechanism.

Consider improving readability by using a separate variable for the schema resolution:

+      const resolvedSchema = schema ?? subSchema ?? computedSchema;
       onChange(
         path,
         value,
-        schema ?? subSchema ?? computedSchema,
+        resolvedSchema,
         computedAncestors
       );
fiftyone/utils/transformers.py (1)

589-591: LGTM! Consistent device handling across model types.

The device handling is implemented consistently across different model types and operations. However, there's an opportunity to reduce code duplication.

Consider extracting the common device handling pattern into a helper method to reduce code duplication. For example:

def _load_model_to_device(self, model_cls, config):
    """Helper to load a model and move it to the correct device."""
    device = torch.device(config.device)
    return model_cls.from_pretrained(config.name_or_path).to(device)

This could then be used across different model loading methods:

def _load_model(self, config):
    if config.model is not None:
        return config.model
    return self._load_model_to_device(
        transformers.AutoModelForImageClassification, config
    )

Also applies to: 649-649, 701-704, 708-708, 756-757, 766-766, 777-779, 790-790, 832-832, 886-886, 938-938, 1090-1090

fiftyone/core/view.py (2)

442-451: Improve exception handling in first() method.

While the implementation is efficient, the exception handling should be improved to maintain the exception chain.

Apply this change to properly chain exceptions:

     try:
         return next(iter(self))
     except StopIteration:
-        raise ValueError("%s is empty" % self.__class__.__name__)
+        raise ValueError("%s is empty" % self.__class__.__name__) from None
🧰 Tools
🪛 Ruff (0.8.2)

451-451: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


489-553: LGTM with minor improvements needed.

The one() method is well-implemented with comprehensive documentation and efficient querying. However, the exception handling should be improved.

Apply these changes to properly chain exceptions:

     try:
         d = next(matches)
     except StopIteration:
-        raise ValueError("No samples match the given expression")
+        raise ValueError("No samples match the given expression") from None
🧰 Tools
🪛 Ruff (0.8.2)

542-542: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

tests/unittests/dataset_tests.py (2)

971-1018: Well structured test for first/last/head/tail dataset operations.

The test provides good coverage of basic functionality but could benefit from additional assertions:

  • Verify that head() and tail() return exactly 3 samples by default
  • Add test cases for custom head/tail sizes
  • Consider testing edge cases like head/tail sizes larger than dataset size
def test_first_last_head_tail(self):
    dataset = fo.Dataset()
    dataset.add_samples(
        [
            fo.Sample(filepath="image%d.jpg" % i, index=i)
            for i in range(1, 52)
        ]
    )

    # Test default behavior
    self.assertEqual(len(dataset.head()), 3)
    self.assertEqual(len(dataset.tail()), 3)
    
    # Test custom sizes
    self.assertEqual(len(dataset.head(5)), 5)
    self.assertEqual(len(dataset.tail(5)), 5)
    
    # Test oversized requests
    self.assertEqual(len(dataset.head(100)), len(dataset))
    self.assertEqual(len(dataset.tail(100)), len(dataset))
    
    # Existing assertions...

1019-1048: Good error handling test for empty dataset operations.

The test thoroughly verifies error handling for empty datasets. Consider adding:

  • Test cases for head/tail with non-default sizes on empty dataset
  • Assertions to verify the specific error message content
def test_first_last_head_tail_empty(self):
    dataset = fo.Dataset()

    with self.assertRaises(ValueError) as cm:
        _ = dataset.first()
    self.assertIn("empty dataset", str(cm.exception))

    # Test custom sizes
    self.assertListEqual(dataset.head(5), [])
    self.assertListEqual(dataset.tail(5), [])
    
    # Existing assertions...
fiftyone/core/dataset.py (2)

1236-1243: Improve error handling in empty dataset case.

Use raise ... from None to properly chain exceptions when the dataset is empty.

 try:
     d = next(cursor)
 except StopIteration:
-    raise ValueError("%s is empty" % self.__class__.__name__)
+    raise ValueError("%s is empty" % self.__class__.__name__) from None
🧰 Tools
🪛 Ruff (0.8.2)

1241-1241: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


Line range hint 1337-1350: Improve error handling in match cases.

The implementation efficiently checks for multiple matches using limit=2. Consider improving exception chaining:

 try:
     d = next(matches)
 except StopIteration:
-    raise ValueError("No samples match the given expression")
+    raise ValueError("No samples match the given expression") from None
fiftyone/core/collections.py (1)

10575-10590: Simplify boolean expression

The boolean expression can be simplified by returning the condition directly.

-        if (
-            isinstance(self, fov.DatasetView)
-            and self._dataset.media_type == fom.GROUP
-            and len(self._stages) == 1
-            and isinstance(self._stages[0], fos.SelectGroupSlices)
-            and self._pipeline() == []
-        ):
-            return True
-
-        return False
+        return (
+            isinstance(self, fov.DatasetView)
+            and self._dataset.media_type == fom.GROUP
+            and len(self._stages) == 1
+            and isinstance(self._stages[0], fos.SelectGroupSlices)
+            and self._pipeline() == []
+        )
🧰 Tools
🪛 Ruff (0.8.2)

10580-10589: Return the condition directly

Inline condition

(SIM103)

app/packages/looker/src/worker/canvas-decoder.ts (1)

Line range hint 54-57: Ensure proper cleanup of imageBitmap.

The imageBitmap.close() call should be in a finally block to ensure cleanup even if subsequent operations fail.

-  offScreenCanvas.width = width;
-  offScreenCanvas.height = height;
-
-  offScreenCanvasCtx.drawImage(imageBitmap, 0, 0);
-
-  imageBitmap.close();
+  try {
+    offScreenCanvas.width = width;
+    offScreenCanvas.height = height;
+    offScreenCanvasCtx.drawImage(imageBitmap, 0, 0);
+  } finally {
+    imageBitmap.close();
+  }
app/packages/looker/src/worker/mask-decoder.ts (1)

54-64: Document channel mapping in JSDoc format.

Add detailed JSDoc comments explaining the channel mapping for each PNG color type.

+      /**
+       * Channel mapping according to PNG specification:
+       * - Type 0: Grayscale (1 channel)
+       * - Type 2: RGB (3 channels, using 4 for compatibility)
+       * - Type 3: Indexed (palette-based, using 4 channels)
+       * - Type 4: Grayscale+Alpha (using 1 channel)
+       * - Type 6: RGBA (4 channels)
+       */
       if (colorType === 0 || colorType === 4) {
         channels = 1;
       } else {
🛑 Comments failed to post (2)
app/packages/state/src/hooks/useCreateLooker.ts (1)

244-245: ⚠️ Potential issue

Add null check for controller retrieval.

The current implementation assumes the controller always exists. Add a null check to handle cases where the controller might be missing.

-            frameStoreController:
-              ImaVidFramesControllerStore.get(imavidPartitionKey),
+            frameStoreController: (() => {
+              const controller = ImaVidFramesControllerStore.get(imavidPartitionKey);
+              if (!controller) {
+                throw new Error(`Frame controller not found for key: ${imavidPartitionKey}`);
+              }
+              return controller;
+            })(),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

            frameStoreController: (() => {
              const controller = ImaVidFramesControllerStore.get(imavidPartitionKey);
              if (!controller) {
                throw new Error(`Frame controller not found for key: ${imavidPartitionKey}`);
              }
              return controller;
            })(),
fiftyone/utils/open_clip.py (1)

61-61: ⚠️ Potential issue

Breaking change: Renamed preprocess attribute.

The attribute has been renamed from preprocess to _preprocess_aux. While the new name is more descriptive, the change from public to private attribute (with underscore prefix) might break external code.

Please ensure this breaking change is documented in the changelog and consider whether it should remain public for backward compatibility.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (3)
app/packages/looker/src/worker/mask-decoder.ts (3)

10-34: 🛠️ Refactor suggestion

Add comprehensive error handling to PNG header parsing

The function should handle potential errors during blob reading and array buffer conversion.

 const getMaybePngHeader = async (
   blob: Blob
 ): Promise<{ bitDepth: number; colorType: number } | undefined> => {
+  try {
+    if (blob.size < 29) {
+      console.warn("PNG file too small to contain valid header");
+      return undefined;
+    }
+
     const header = new Uint8Array(await blob.slice(0, 29).arrayBuffer());
 
     // check PNG signature
     for (let i = 0; i < PNG_SIGNATURE.length; i++) {
       if (header[i] !== PNG_SIGNATURE[i]) {
-        // not a PNG
         return undefined;
       }
     }
 
     const bitDepth = header[24];
     const colorType = header[25];
+
+    // Validate bit depth and color type
+    if (![1, 2, 4, 8, 16].includes(bitDepth)) {
+      console.warn(`Invalid bit depth: ${bitDepth}`);
+      return undefined;
+    }
+
     return { bitDepth, colorType };
+  } catch (error) {
+    console.error("Error reading PNG header:", error);
+    return undefined;
+  }
 };

49-53: 🛠️ Refactor suggestion

Improve error handling for 16-bit PNG decoding

The customDecode16BitPng call needs proper error handling.

     if (colorType !== undefined) {
       if (bitDepth === 16) {
         // browser doesn't natively parse 16 bit pngs, we'll use a 3pp library
-        return customDecode16BitPng(blob);
+        try {
+          return await customDecode16BitPng(blob);
+        } catch (error) {
+          console.error("Failed to decode 16-bit PNG:", error);
+          return decodeWithCanvas(blob, cls, channels);
+        }
       }

61-65: 🛠️ Refactor suggestion

Add validation for supported color types

The code should explicitly validate supported color types.

+      const validColorTypes = [0, 2, 3, 4, 6];
+      if (!validColorTypes.includes(colorType)) {
+        console.warn(`Unsupported PNG color type: ${colorType}`);
+        return decodeWithCanvas(blob, cls, channels);
+      }
+
       if (colorType === 0 || colorType === 4) {
         channels = 1;
       } else {
         channels = 4;
       }
🧹 Nitpick comments (2)
app/packages/looker/src/worker/mask-decoder.ts (2)

1-4: Consider adding type safety to PNG_SIGNATURE constant

The PNG signature constant could benefit from explicit typing for better maintainability and type safety.

-const PNG_SIGNATURE = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];
+const PNG_SIGNATURE: readonly number[] = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a] as const;

55-66: Document channel selection logic with JSDoc

The channel selection logic should be documented for better maintainability.

+      /**
+       * Channel selection based on PNG color type:
+       * - Type 0 (Grayscale): 1 channel
+       * - Type 2 (RGB): 4 channels (with alpha)
+       * - Type 3 (Indexed): 4 channels
+       * - Type 4 (Grayscale+Alpha): 1 channel
+       * - Type 6 (RGBA): 4 channels
+       */
       if (colorType === 0 || colorType === 4) {
         channels = 1;
       } else {
         channels = 4;
       }
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dff2887 and 2aeed5a.

📒 Files selected for processing (1)
  • app/packages/looker/src/worker/mask-decoder.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
app/packages/looker/src/worker/mask-decoder.ts (1)

Pattern **/*.{ts,tsx}: Review the Typescript and React code for conformity with best practices in React, Recoil, Graphql, and Typescript. Highlight any deviations.

Copy link
Contributor

@benjaminpkane benjaminpkane left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 🖼️

@sashankaryal sashankaryal merged commit ab798d5 into release/v1.3.0 Jan 22, 2025
8 of 11 checks passed
@sashankaryal sashankaryal deleted the fix/16-bit-png branch January 22, 2025 18:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants