Keypress and slider end times calculation
This commit is contained in:
parent
2376fe733e
commit
2adbc5c5d1
@ -244,6 +244,37 @@ open class Scores(
|
||||
*/
|
||||
val VERSION: TableField<ScoresRecord, Int?> = createField(DSL.name("version"), SQLDataType.INTEGER.defaultValue(DSL.field(DSL.raw("0"), SQLDataType.INTEGER)), this, "")
|
||||
|
||||
/**
|
||||
* The column <code>public.scores.keypresses_times</code>.
|
||||
*/
|
||||
val KEYPRESSES_TIMES: TableField<ScoresRecord, Array<Double?>?> = createField(DSL.name("keypresses_times"), SQLDataType.FLOAT.array(), this, "")
|
||||
|
||||
/**
|
||||
* The column <code>public.scores.keypresses_median</code>.
|
||||
*/
|
||||
val KEYPRESSES_MEDIAN: TableField<ScoresRecord, Double?> = createField(DSL.name("keypresses_median"), SQLDataType.DOUBLE, this, "")
|
||||
|
||||
/**
|
||||
* The column <code>public.scores.keypresses_standard_deviation</code>.
|
||||
*/
|
||||
val KEYPRESSES_STANDARD_DEVIATION: TableField<ScoresRecord, Double?> = createField(DSL.name("keypresses_standard_deviation"), SQLDataType.DOUBLE, this, "")
|
||||
|
||||
/**
|
||||
* The column <code>public.scores.sliderend_release_times</code>.
|
||||
*/
|
||||
val SLIDEREND_RELEASE_TIMES: TableField<ScoresRecord, Array<Double?>?> = createField(DSL.name("sliderend_release_times"), SQLDataType.FLOAT.array(), this, "")
|
||||
|
||||
/**
|
||||
* The column <code>public.scores.sliderend_release_median</code>.
|
||||
*/
|
||||
val SLIDEREND_RELEASE_MEDIAN: TableField<ScoresRecord, Double?> = createField(DSL.name("sliderend_release_median"), SQLDataType.DOUBLE, this, "")
|
||||
|
||||
/**
|
||||
* The column
|
||||
* <code>public.scores.sliderend_release_standard_deviation</code>.
|
||||
*/
|
||||
val SLIDEREND_RELEASE_STANDARD_DEVIATION: TableField<ScoresRecord, Double?> = createField(DSL.name("sliderend_release_standard_deviation"), SQLDataType.DOUBLE, this, "")
|
||||
|
||||
private constructor(alias: Name, aliased: Table<ScoresRecord>?): this(alias, null, null, aliased, null)
|
||||
private constructor(alias: Name, aliased: Table<ScoresRecord>?, parameters: Array<Field<*>?>?): this(alias, null, null, aliased, parameters)
|
||||
|
||||
|
||||
@ -161,6 +161,30 @@ open class ScoresRecord private constructor() : UpdatableRecordImpl<ScoresRecord
|
||||
set(value): Unit = set(34, value)
|
||||
get(): Int? = get(34) as Int?
|
||||
|
||||
open var keypressesTimes: Array<Double?>?
|
||||
set(value): Unit = set(35, value)
|
||||
get(): Array<Double?>? = get(35) as Array<Double?>?
|
||||
|
||||
open var keypressesMedian: Double?
|
||||
set(value): Unit = set(36, value)
|
||||
get(): Double? = get(36) as Double?
|
||||
|
||||
open var keypressesStandardDeviation: Double?
|
||||
set(value): Unit = set(37, value)
|
||||
get(): Double? = get(37) as Double?
|
||||
|
||||
open var sliderendReleaseTimes: Array<Double?>?
|
||||
set(value): Unit = set(38, value)
|
||||
get(): Array<Double?>? = get(38) as Array<Double?>?
|
||||
|
||||
open var sliderendReleaseMedian: Double?
|
||||
set(value): Unit = set(39, value)
|
||||
get(): Double? = get(39) as Double?
|
||||
|
||||
open var sliderendReleaseStandardDeviation: Double?
|
||||
set(value): Unit = set(40, value)
|
||||
get(): Double? = get(40) as Double?
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Primary key information
|
||||
// -------------------------------------------------------------------------
|
||||
@ -170,7 +194,7 @@ open class ScoresRecord private constructor() : UpdatableRecordImpl<ScoresRecord
|
||||
/**
|
||||
* Create a detached, initialised ScoresRecord
|
||||
*/
|
||||
constructor(id: Int? = null, beatmapId: Int? = null, count_100: Int? = null, count_300: Int? = null, count_50: Int? = null, countMiss: Int? = null, date: LocalDateTime? = null, maxCombo: Int? = null, mods: Int? = null, perfect: Boolean? = null, pp: Double? = null, rank: String? = null, replayAvailable: Boolean? = null, replayId: Long? = null, score: Long? = null, userId: Long? = null, replay: ByteArray? = null, ur: Double? = null, frametime: Double? = null, edgeHits: Int? = null, snaps: Int? = null, isBanned: Boolean? = null, adjustedUr: Double? = null, meanError: Double? = null, errorVariance: Double? = null, errorStandardDeviation: Double? = null, minimumError: Double? = null, maximumError: Double? = null, errorRange: Double? = null, errorCoefficientOfVariation: Double? = null, errorKurtosis: Double? = null, errorSkewness: Double? = null, sentDiscordNotification: Boolean? = null, addedAt: OffsetDateTime? = null, version: Int? = null): this() {
|
||||
constructor(id: Int? = null, beatmapId: Int? = null, count_100: Int? = null, count_300: Int? = null, count_50: Int? = null, countMiss: Int? = null, date: LocalDateTime? = null, maxCombo: Int? = null, mods: Int? = null, perfect: Boolean? = null, pp: Double? = null, rank: String? = null, replayAvailable: Boolean? = null, replayId: Long? = null, score: Long? = null, userId: Long? = null, replay: ByteArray? = null, ur: Double? = null, frametime: Double? = null, edgeHits: Int? = null, snaps: Int? = null, isBanned: Boolean? = null, adjustedUr: Double? = null, meanError: Double? = null, errorVariance: Double? = null, errorStandardDeviation: Double? = null, minimumError: Double? = null, maximumError: Double? = null, errorRange: Double? = null, errorCoefficientOfVariation: Double? = null, errorKurtosis: Double? = null, errorSkewness: Double? = null, sentDiscordNotification: Boolean? = null, addedAt: OffsetDateTime? = null, version: Int? = null, keypressesTimes: Array<Double?>? = null, keypressesMedian: Double? = null, keypressesStandardDeviation: Double? = null, sliderendReleaseTimes: Array<Double?>? = null, sliderendReleaseMedian: Double? = null, sliderendReleaseStandardDeviation: Double? = null): this() {
|
||||
this.id = id
|
||||
this.beatmapId = beatmapId
|
||||
this.count_100 = count_100
|
||||
@ -206,6 +230,12 @@ open class ScoresRecord private constructor() : UpdatableRecordImpl<ScoresRecord
|
||||
this.sentDiscordNotification = sentDiscordNotification
|
||||
this.addedAt = addedAt
|
||||
this.version = version
|
||||
this.keypressesTimes = keypressesTimes
|
||||
this.keypressesMedian = keypressesMedian
|
||||
this.keypressesStandardDeviation = keypressesStandardDeviation
|
||||
this.sliderendReleaseTimes = sliderendReleaseTimes
|
||||
this.sliderendReleaseMedian = sliderendReleaseMedian
|
||||
this.sliderendReleaseStandardDeviation = sliderendReleaseStandardDeviation
|
||||
resetChangedOnNotNull()
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,6 +74,15 @@ class CircleguardService {
|
||||
var error_coefficient_of_variation: Double?,
|
||||
var error_kurtosis: Double?,
|
||||
var error_skewness: Double?,
|
||||
|
||||
val keypresses_times: List<Double>?,
|
||||
val keypresses_median: Double?,
|
||||
val keypresses_standard_deviation: Double?,
|
||||
|
||||
val sliderend_release_times: List<Double>?,
|
||||
val sliderend_release_median: Double?,
|
||||
val sliderend_release_standard_deviation: Double?,
|
||||
|
||||
val judgements: List<ScoreJudgement>
|
||||
)
|
||||
|
||||
|
||||
@ -31,19 +31,19 @@ class FixOldScores(
|
||||
@Value("\${OLD_SCORES_PAGE_SIZE:5000}")
|
||||
private var pageSize: Int = 5000
|
||||
|
||||
val CURRENT_VERSION = 1
|
||||
val CURRENT_VERSION = 2
|
||||
|
||||
private val logger = LoggerFactory.getLogger(javaClass)
|
||||
|
||||
data class Task(val offset: Int, val limit: Int)
|
||||
|
||||
@Scheduled(fixedDelay = 40000, initialDelay = 0)
|
||||
@Scheduled(fixedDelay = 120000, initialDelay = 0)
|
||||
fun fixOldScores() {
|
||||
val condition = SCORES.REPLAY.isNotNull.and(SCORES.VERSION.lessThan(CURRENT_VERSION))
|
||||
val totalRows = dslContext.fetchCount(SCORES, condition)
|
||||
|
||||
if(totalRows <= 0) {
|
||||
this.logger.warn("Fixing old scores but there are none, total rows: $totalRows")
|
||||
this.logger.debug("Fixing old scores but there are none, total rows: $totalRows")
|
||||
return
|
||||
}
|
||||
|
||||
@ -104,6 +104,7 @@ class FixOldScores(
|
||||
).get()
|
||||
} catch (e: Exception) {
|
||||
this.logger.error("Circleguard failed to process replay with score_id: ${score.id}")
|
||||
this.logger.error(e.stackTraceToString())
|
||||
return
|
||||
}
|
||||
|
||||
@ -128,6 +129,12 @@ class FixOldScores(
|
||||
.set(SCORES.ERROR_SKEWNESS, processedReplay.error_skewness)
|
||||
.set(SCORES.SNAPS, processedReplay.snaps)
|
||||
.set(SCORES.EDGE_HITS, processedReplay.edge_hits)
|
||||
.set(SCORES.KEYPRESSES_TIMES, processedReplay.keypresses_times?.toTypedArray())
|
||||
.set(SCORES.KEYPRESSES_MEDIAN, processedReplay.keypresses_median)
|
||||
.set(SCORES.KEYPRESSES_STANDARD_DEVIATION, processedReplay.keypresses_standard_deviation)
|
||||
.set(SCORES.SLIDEREND_RELEASE_TIMES, processedReplay.sliderend_release_times?.toTypedArray())
|
||||
.set(SCORES.SLIDEREND_RELEASE_MEDIAN, processedReplay.sliderend_release_median)
|
||||
.set(SCORES.SLIDEREND_RELEASE_STANDARD_DEVIATION, processedReplay.sliderend_release_standard_deviation)
|
||||
.where(SCORES.REPLAY_ID.eq(score.replayId))
|
||||
.returningResult(SCORES.ID)
|
||||
.fetchOne()?.getValue(SCORES.ID)
|
||||
|
||||
@ -64,7 +64,7 @@ class ImportScores(
|
||||
}
|
||||
}
|
||||
|
||||
val CURRENT_VERSION = 1
|
||||
val CURRENT_VERSION = 2
|
||||
|
||||
@Value("\${WEBHOOK_URL}")
|
||||
private lateinit var webhookUrl: String
|
||||
|
||||
@ -0,0 +1,7 @@
|
||||
ALTER TABLE public.scores
|
||||
ADD COLUMN keypresses_times float8[],
|
||||
ADD COLUMN keypresses_median float8,
|
||||
ADD COLUMN keypresses_standard_deviation float8,
|
||||
ADD COLUMN sliderend_release_times float8[],
|
||||
ADD COLUMN sliderend_release_median float8,
|
||||
ADD COLUMN sliderend_release_standard_deviation float8;
|
||||
@ -1,3 +1,4 @@
|
||||
ossapi==3.4.3
|
||||
circleguard==5.4.1
|
||||
flask==3.0.2
|
||||
flask==3.0.2
|
||||
brparser==1.0.4
|
||||
89
nise-circleguard/src/keypresses.py
Normal file
89
nise-circleguard/src/keypresses.py
Normal file
@ -0,0 +1,89 @@
|
||||
from brparser import Keypress, Key, apply_mods_to_time, diff_range, HitCircleOsu, SliderOsu, SpinnerOsu
|
||||
|
||||
|
||||
def get_keypresses(replay):
|
||||
keypresses = []
|
||||
button_1 = False
|
||||
button_2 = False
|
||||
for event in replay.replay_data:
|
||||
if (event.keys & Key.M1):
|
||||
if not button_1:
|
||||
button_1 = True
|
||||
init_1 = event.time
|
||||
else:
|
||||
if button_1:
|
||||
button_1 = False
|
||||
keypresses.append(Keypress(init_1, event.time))
|
||||
if (event.keys & Key.M2):
|
||||
if not button_2:
|
||||
button_2 = True
|
||||
init_2 = event.time
|
||||
else:
|
||||
if button_2:
|
||||
button_2 = False
|
||||
keypresses.append(Keypress(init_2, event.time))
|
||||
return keypresses
|
||||
|
||||
|
||||
def get_kp_sliders(replay, beatmap):
|
||||
keypresses = get_keypresses(replay)
|
||||
hw_50 = int(diff_range(beatmap.od, 200, 150, 100, replay.mods))
|
||||
|
||||
keypress_times = []
|
||||
sliderend_release_times = []
|
||||
hit_objs = beatmap.hit_objects
|
||||
|
||||
kp_i = 0
|
||||
ho_i = 0
|
||||
|
||||
while ho_i < len(hit_objs) and kp_i < len(keypresses):
|
||||
ho = hit_objs[ho_i]
|
||||
kp = keypresses[kp_i]
|
||||
|
||||
if isinstance(ho, HitCircleOsu):
|
||||
end_time = ho.start_time + hw_50 + 1
|
||||
else:
|
||||
end_time = ho.end_time
|
||||
|
||||
if kp.key_down < ho.start_time - 400:
|
||||
kp_i += 1
|
||||
continue
|
||||
|
||||
if kp.key_down <= ho.start_time - hw_50:
|
||||
if isinstance(ho, SliderOsu):
|
||||
release_time = kp.key_up - ho.end_time
|
||||
sliderend_release_times.append(
|
||||
apply_mods_to_time(release_time, replay.mods))
|
||||
while keypresses[kp_i].key_down < ho.end_time:
|
||||
kp_i += 1
|
||||
if kp_i >= len(keypresses):
|
||||
break
|
||||
else:
|
||||
if isinstance(ho, HitCircleOsu):
|
||||
keypress_time = kp.key_up - kp.key_down
|
||||
keypress_times.append(apply_mods_to_time(keypress_time,
|
||||
replay.mods))
|
||||
kp_i += 1
|
||||
ho_i += 1
|
||||
elif kp.key_down >= end_time:
|
||||
ho_i += 1
|
||||
else:
|
||||
if kp.key_down < ho.start_time + hw_50 and not isinstance(ho, SpinnerOsu):
|
||||
if isinstance(ho, SliderOsu):
|
||||
release_time = kp.key_up - ho.end_time
|
||||
sliderend_release_times.append(
|
||||
apply_mods_to_time(release_time, replay.mods))
|
||||
while keypresses[kp_i].key_down < ho.end_time:
|
||||
kp_i += 1
|
||||
if kp_i >= len(keypresses):
|
||||
break
|
||||
else:
|
||||
keypress_time = kp.key_up - kp.key_down
|
||||
keypress_times.append(apply_mods_to_time(keypress_time,
|
||||
replay.mods))
|
||||
kp_i += 1
|
||||
ho_i += 1
|
||||
else:
|
||||
kp_i += 1
|
||||
|
||||
return keypress_times, sliderend_release_times
|
||||
@ -1,3 +1,4 @@
|
||||
import base64
|
||||
import io
|
||||
import os
|
||||
from dataclasses import dataclass, asdict
|
||||
@ -7,10 +8,12 @@ from typing import List, Iterable
|
||||
|
||||
import numpy as np
|
||||
import scipy
|
||||
from brparser import Replay, BeatmapOsu, Mod
|
||||
from circleguard import Circleguard, ReplayString, Hit
|
||||
from flask import Flask, request, jsonify, abort
|
||||
|
||||
from src.WriteStreamWrapper import WriteStreamWrapper
|
||||
from src.keypresses import get_kp_sliders
|
||||
|
||||
# Circleguard
|
||||
cg = Circleguard(os.getenv("OSU_API_KEY"), db_path="./dbs/db.db", slider_dir="./dbs/")
|
||||
@ -55,6 +58,14 @@ class ReplayResponse:
|
||||
error_kurtosis: float
|
||||
error_skewness: float
|
||||
|
||||
keypresses_times: List[int]
|
||||
keypresses_median: float
|
||||
keypresses_standard_deviation: float
|
||||
|
||||
sliderend_release_times: List[int]
|
||||
sliderend_release_median: float
|
||||
sliderend_release_standard_deviation: float
|
||||
|
||||
judgements: List[Hit]
|
||||
|
||||
def to_dict(self):
|
||||
@ -100,6 +111,22 @@ def process_replay():
|
||||
edge_hits = sum(1 for _ in cg.hits(replay=replay1, within=1, beatmap=cg_beatmap))
|
||||
snaps = sum(1 for _ in cg.snaps(replay=replay1, beatmap=cg_beatmap))
|
||||
|
||||
#
|
||||
# Decode the base64 string
|
||||
decoded_data = base64.b64decode(replay_request.replay_data)
|
||||
|
||||
# Pass the decoded data to the Replay class
|
||||
replay = Replay(decoded_data, pure_lzma=True)
|
||||
replay.mods = Mod(replay_request.mods)
|
||||
|
||||
beatmap_file = f'dbs/{cg_beatmap.artist} - {cg_beatmap.title} ({cg_beatmap.creator})[{cg_beatmap.version}].osu'
|
||||
if not os.path.exists(beatmap_file):
|
||||
print(f'Map not found @ {beatmap_file}', flush=True)
|
||||
return 400, "Map not found"
|
||||
|
||||
beatmap = BeatmapOsu(f'dbs/{cg_beatmap.artist} - {cg_beatmap.title} ({cg_beatmap.creator})[{cg_beatmap.version}].osu')
|
||||
kp, se = get_kp_sliders(replay, beatmap)
|
||||
|
||||
hits: Iterable[Hit] = cg.hits(replay=replay1, beatmap=cg_beatmap)
|
||||
judgements: List[ScoreJudgement] = []
|
||||
for hit in hits:
|
||||
@ -143,6 +170,14 @@ def process_replay():
|
||||
error_kurtosis=kurtosis,
|
||||
error_skewness=skewness,
|
||||
|
||||
keypresses_times=kp,
|
||||
keypresses_median=np.median(kp),
|
||||
keypresses_standard_deviation=np.std(kp, ddof=1),
|
||||
|
||||
sliderend_release_times=se,
|
||||
sliderend_release_median=np.median(se),
|
||||
sliderend_release_standard_deviation=np.std(se, ddof=1),
|
||||
|
||||
judgements=judgements
|
||||
)
|
||||
return jsonify(ur_response.to_dict())
|
||||
|
||||
Loading…
Reference in New Issue
Block a user