Skip to main content

A Qt Widget for visualizing osu! beatmaps and replays.

Project description

Circlevis

Circlevis is the replay viewer (aka visualizer) in Circleguard. It was split off into its own repository to allow other projects to use it, should they so choose.

Circlevis is a pyqt widget.

Installation

Circlevis can be installed from pip:

pip install circlevis

Usage

Circlevis can be used in two ways:

VisualizerApp

The easiest way is to instantiate a VisualizerApp, which subclasses QApplication so you don't have to create a main application yourself. This is best for quick visualization, when you only want to open circlevis and nothing else.

from circleguard import *
from circlevis import VisualizerApp, BeatmapInfo

cg = Circleguard("key")
r = ReplayMap(509610, 6304246)
# replays must be loaded before passed to the visualizer
cg.load(r)

# BeatmapInfo tells circlevis how it should load the beatmap before it displays
# it. You can pass either a map id (in which case circlevis will download the map
# from osu!'s servers) or a path to a .osu file (in which case circlevis will
# load the beatmap from that file).
# If you don't want any beatmap to be displayed, instantiate an empty BeatmapInfo
# (bm = BeatmapInfo()) and pass that to the visualizer.
bm = BeatmapInfo(map_id=r.map_id)
app = VisualizerApp(bm, replays=[r])
# this calls qt's `exec` function, which shows the application and enters the
# gui run loop, blocking any code after this call.
app.exec()

You can also visualize only a map, without any replay:

from circlevis import VisualizerApp, BeatmapInfo

bm = BeatmapInfo(map_id=509610)
app = VisualizerApp(bm)
app.exec()

Visualizer

If you want to integrate the visualizer into an existing project (which already has its own QApplication), you should instead instantiate the Visualizer class. Visualizer subclasses QMainWindow instead of QApplication and can be used like any other widget.

from circleguard import *
from circlevis import Visualizer, BeatmapInfo

cg = Circleguard("key")
r = ReplayMap(509610, 6304246)
cg.load(r)

bm = BeatmapInfo(map_id=r.map_id)
visualizer_window = Visualizer(bm, replays=[r])
visualizer_window.show()
# or do something fancy with it instead of showing it immediately

Other Arguments

Both VisualizerApp and Visualizer can take several optional arguments:

  • events - a list of timestamps (in ms). If a frame with that timestamp is found in the replay, it is colored gold
  • library - A slider Library class, which will be used instead of creating a new one if passed
  • speeds - a list of possible speeds the visualizer can play at. These can be switched between in real time with the speed up or speed down icons on the visualizer, or by pressing the up or down keys
  • start_speed - which speed to start playback at. This value must be in speeds
  • paint_info - whether to draw information about the map and replays in the upper left hand corner

Classifier

Circlevis also provides a Classifier class, which builds on the visualizer to provide an easy way to batch classify replays one at a time. For instance, imagine you want to go through a map's leaderboard and assign a "cursordance score" to each replay, depending on how often the user cursordanced. The classifier will show you the first replay and wait for you to press a number key that assigns a cursordance score to that replay. When you do so, it saves the score and shows the next replay. Repeat until all replays are classified.

To use, you need a list of hotkeys that you will use to control the classification of the replays, a circleguard instance, and a list of Replay instances. Here's an example for the aforementioned "cursordance scoring" use case, where you can assign replays a score from 1 to 10:

from collections import defaultdict
from circleguard import Circleguard
from circlevis import Classifier, ClassifierHotkey

cg = Circleguard("api_key")

class JudgeClassifier(Classifier):
    def __init__(self, replays, cg):

        self.scores = defaultdict(list)

        hotkeys = [
            ClassifierHotkey(Qt.Key_1, lambda r: self.assign_score(1, r)),
            ClassifierHotkey(Qt.Key_2, lambda r: self.assign_score(2, r)),
            ClassifierHotkey(Qt.Key_3, lambda r: self.assign_score(3, r)),
            ClassifierHotkey(Qt.Key_4, lambda r: self.assign_score(4, r)),
            ClassifierHotkey(Qt.Key_5, lambda r: self.assign_score(5, r)),
            ClassifierHotkey(Qt.Key_6, lambda r: self.assign_score(6, r)),
            ClassifierHotkey(Qt.Key_7, lambda r: self.assign_score(7, r)),
            ClassifierHotkey(Qt.Key_8, lambda r: self.assign_score(8, r)),
            ClassifierHotkey(Qt.Key_9, lambda r: self.assign_score(9, r)),
            ClassifierHotkey(Qt.Key_0, lambda r: self.assign_score(10, r)),
        ]
        super().__init__(replays, cg, hotkeys)

    def assign_score(self, score, replay):
        print(f"scoring {replay} as a {score}")
        self.scores[score].append(replay)
        # show the next replay now that we've scored this one
        self.next_replay()

    def done(self):
        print(f"final scores: {self.scores}")

replays = cg.Map(221777, "1-10")
classifier = JudgeClassifier(replays, cg)
classifier.start()

Programmatically Taking Screenshots

A cookbook recipe to save the current state of the visualizer at arbitrary timestamps in the map:

from circleguard import *
from circlevis import *

cg = Circleguard("api_key")

r = cg.Map(2102290, "1", mods=Mod.HD, load=True)[0]
bm = BeatmapInfo(map_id=r.map_id)
screenshot_times = [727, 8000, 15214]

class ScreenshotVisualizer(VisualizerApp):
    def on_load(self):
        self.pause()
        for i, t in enumerate(screenshot_times):
            self.seek_to(t)
            image = self.save_as_image()
            image.save(f"image-{i}.png")
        self.exit()

vis = ScreenshotVisualizer(bm, [r])
vis.exec()

If you want to take screenshots of multiple replays over multiple maps, it gets a bit trickier because we can only instantiate one QApplication over the lifetime of the program, even if we try to instantiate them in sequence. But we can still slightly abuse Classifier to achieve this:

m = cg.Map(221777, "1-2", load=True)
# fill in with whatever screenshot times you want
screenshot_times = {
    m[0]: [123, 234, 456],
    m[1]: [10000, 20000, 30000]
}

class ScreenshotVisualizer(Visualizer):
    def __init__(self, callback, screenshot_times, *args, **kwargs):
        self.callback = callback
        self.screenshot_times = screenshot_times
        super().__init__(*args, **kwargs)

    def on_load(self):
        self.pause()
        for t in self.screenshot_times:
            self.seek_to(t)
            image = self.save_as_image()
            image.save(f"replay-{self.replays[0].username}-{t}.png")
        self.callback()

class ScreenshotClassifier(Classifier):
    def visualizer(self, bm, replay):
        callback = lambda: self.next_replay()
        times = screenshot_times[replay]
        return ScreenshotVisualizer(callback, times, bm, [replay])

c = ScreenshotClassifier(m, cg, [])
c.start()

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

circlevis-1.4.6.tar.gz (126.3 kB view details)

Uploaded Source

Built Distribution

circlevis-1.4.6-py3-none-any.whl (143.2 kB view details)

Uploaded Python 3

File details

Details for the file circlevis-1.4.6.tar.gz.

File metadata

  • Download URL: circlevis-1.4.6.tar.gz
  • Upload date:
  • Size: 126.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.4.1 importlib_metadata/3.7.3 pkginfo/1.7.0 requests/2.26.0 requests-toolbelt/0.9.1 tqdm/4.62.2 CPython/3.9.0

File hashes

Hashes for circlevis-1.4.6.tar.gz
Algorithm Hash digest
SHA256 a4ae5610d0bb98049ca3a0a9002172f74126ebcd09f1b2f47a6e9324e97fad30
MD5 53242214bebcebb87677fd0864b365d4
BLAKE2b-256 35d63e0d429f36e4d46979025507b530841d4fadafe051b5d4136b87936a1ca1

See more details on using hashes here.

File details

Details for the file circlevis-1.4.6-py3-none-any.whl.

File metadata

  • Download URL: circlevis-1.4.6-py3-none-any.whl
  • Upload date:
  • Size: 143.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.4.1 importlib_metadata/3.7.3 pkginfo/1.7.0 requests/2.26.0 requests-toolbelt/0.9.1 tqdm/4.62.2 CPython/3.9.0

File hashes

Hashes for circlevis-1.4.6-py3-none-any.whl
Algorithm Hash digest
SHA256 b5f2f7d6f00fbfbb0f4b36a88c8be86820d215f4a9e1e9feb65b21bfa465a266
MD5 ae85d52eca5713f49fc3a53d53001d88
BLAKE2b-256 84785d8d715ce84cb7232a8ef6bea2f1b3f513589f69e6858746fae1825a17ac

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page