-
-
Notifications
You must be signed in to change notification settings - Fork 150
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
feat: Enhance take_screenshot for multi-monitor support #792
base: main
Are you sure you want to change the base?
feat: Enhance take_screenshot for multi-monitor support #792
Conversation
Thank you @onyedikachi-david ! What do you think about saving taking a screenshot of all of the available monitors, rather than just the active one? This can provide useful additional context to the model if something changes on one of the screens. |
Okay, I'll implement it. just that I was being mindful of how it is being used across the codebase. If there are more than one monitors, then a tuple or list will be returned, which will affect the |
Thank you please do. Maybe keep the current functionality behind a config param. |
New def take_screenshot() -> list:
"""Take screenshots of the current monitor or all available monitors.
Returns:
list of PIL.Image.Image: A list of screenshot images.
"""
with mss.mss() as sct:
monitors = sct.monitors
screenshots = []
if config.CAPTURE_ALL_MONITORS:
for monitor in monitors[1:]: # Skip the first entry which is a union of all monitors
sct_img = sct.grab(monitor)
image = Image.frombytes("RGB", sct_img.size, sct_img.bgra, "raw", "BGRX")
screenshots.append(image)
else:
current_monitor = get_current_monitor(monitors)
sct_img = sct.grab(current_monitor)
image = Image.frombytes("RGB", sct_img.size, sct_img.bgra, "raw", "BGRX")
screenshots.append(image)
return screenshots If I got you right, I should go ahead and change it to have a list return type, if that is the case, then its current implementation in @classmethod
def take_screenshot(cls: "Screenshot") -> "Screenshot":
"""Capture a screenshot."""
image = utils.take_screenshot()
screenshot = Screenshot(image=image)
return screenshot will change to this: @classmethod
def take_screenshot(cls: "Screenshot") -> list:
"""Capture a screenshot."""
images = take_screenshot(all_monitors=all_monitors)
screenshots = [cls(image=image) for image in images]
return screenshots And its usage will also change: screenshots = Screenshot.take_screenshot(all_monitors=True)
for idx, screenshot in enumerate(screenshots):
screenshot.image.show(title=f"Monitor {idx + 1}") |
I think we want to return a single PIL.Image containing all screenshots. Any way to determine relative positioning of the screens? |
Yes, it is best to return PIL.Image. Let me think of a way to go about it. |
@abrichr I just implemented the requested changes. |
@abrichr I don't know if you've found time to review the PR. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for putting this together @onyedikachi-david ! And thank you for your patience.
I left a few comments concerning performance. Because we call take_screenshot
in a tight loop during recording, it's important that it is as performant as possible.
Can you please run python -m openadapt.record
with CAPTURE_ALL_MONITORS = True
and = False
(while recording similar behavior, e.g. opening the calculator and clicking 2 x 3), and paste the performance plots here? (The path to the performance plot is logged to stdout at the end of the recording.)
openadapt/utils.py
Outdated
return image | ||
PIL.Image.Image: The screenshot image. | ||
""" | ||
with mss.mss() as sct: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to re-use the global SCT
here? Creating a new instance impacts performance; see https://python-mss.readthedocs.io/usage.html#intensive-use
openadapt/utils.py
Outdated
combined_image = Image.new("RGB", (total_width, total_height)) | ||
|
||
for monitor in monitors[1:]: # Skip the first entry which is a union of all monitors | ||
sct_img = sct.grab(monitor) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to grab once and then recombine? Again the issue is performance.
You're welcome. Thanks for the feedback and all. Two issues though:
Just give me awhile to implement the requested changes. |
Please review @abrichr, just implemented the requested changes, no screenshot yet though. Yet to fix out my Mac OS crash |
if config.CAPTURE_ALL_MONITORS: | ||
# Grab all monitors at once | ||
sct_img = SCT.grab(SCT.monitors[0]) # Grab the union of all monitors | ||
full_img = Image.frombytes("RGB", sct_img.size, sct_img.bgra, "raw", "BGRX") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@onyedikachi-david can you please clarify why the rest of this block is necessary? Why not just return full_img
directly?
|
||
config = Config() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should not be necessary. Please replace with from openadapt.config import config
like it's used elsewhere.
@abrichr I'm sorry for keeping a stale PR. I hope it isn't blocking anything. I'm still trying to get my macOS setup together. I was using a Hackintosh (dual-booted with Linux), but it got corrupted while I was reinstalling Docker. I'm still figuring out how to resolve this without making things worse. I don't want to lose my Linux files/partition, which is still functional. |
@onyedikachi-david thank you for the update. For now it is not blocking. 🙏 |
Okay. |
Fixes #766
/claim #766
What kind of change does this PR introduce?
Feature
Summary
This PR introduces support for multi-monitor setups in the
take_screenshot
function. It includes the following changes:get_current_monitor
to determine the monitor where the cursor is currently located.take_screenshot
to useget_current_monitor
for capturing the correct monitor.take_screenshot
with multiple monitors.get_current_monitor
andmss.mss
in tests to simulate multiple monitor configurations.take_screenshot
correctly handles multiple monitors and returns the expected screenshot.Checklist
How can your code be run and tested?
To run and test the code:
pytest
:Other information
No additional context needed.