Skip to content

Commit a75c17d

Browse files
committed
using inside bounding box
1 parent cc3b177 commit a75c17d

File tree

3 files changed

+124
-120
lines changed

3 files changed

+124
-120
lines changed

FitCount/Workout/QuickPoseBasicView.swift

Lines changed: 90 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ enum ViewState: Equatable {
1919
case startVolume
2020
case instructions
2121
case introBoundingBox
22-
case boundingBox(enterTime: Date)
22+
case boundingBox
2323
case introExercise(Exercise)
2424
case exercise(SessionData, enterTime: Date)
2525
case results(SessionData)
@@ -36,6 +36,15 @@ enum ViewState: Equatable {
3636
return nil
3737
}
3838
}
39+
40+
var features: [QuickPose.Feature]? {
41+
switch self {
42+
case .introBoundingBox, .boundingBox:
43+
return [.inside(edgeInsets: QuickPose.RelativeCameraEdgeInsets(top: 0.1, left: 0.2, bottom: 0.01, right: 0.2))]
44+
default:
45+
return nil
46+
}
47+
}
3948
}
4049

4150
struct QuickPoseBasicView: View {
@@ -47,18 +56,12 @@ struct QuickPoseBasicView: View {
4756
@State private var feedbackText: String? = nil
4857

4958
@State private var counter = QuickPoseThresholdCounter()
59+
@State private var unchanged = QuickPoseDoubleUnchangedDetector(similarDuration: 2, leniency: 0)
5060
@State private var state: ViewState = .startVolume
5161

62+
@State private var boundingBoxVisibility = 1.0
5263
@State private var countScale = 1.0
5364
@State private var boundingBoxMaskWidth = 0.0
54-
55-
func canMoveFromBoundingBox(landmarks: QuickPose.Landmarks) -> Bool {
56-
57-
let xsInBox = landmarks.poseLandmarks.allSatisfy { 0.5 - (0.6/2) < $0[0] && $0[0] < 0.5 + (0.6/2) }
58-
let ysInBox = landmarks.poseLandmarks.allSatisfy { 0.5 - (0.8/2) < $0[1] && $0[1] < 0.5 + (0.8/2) }
59-
60-
return xsInBox && ysInBox
61-
}
6265

6366
var body: some View {
6467
GeometryReader { geometry in
@@ -102,12 +105,12 @@ struct QuickPoseBasicView: View {
102105
}
103106
.frame(width: geometry.size.width * 0.6, height: geometry.size.height * 0.8)
104107
.padding(.horizontal, (geometry.size.width * 1 - 0.6)/2)
105-
108+
106109
case .boundingBox:
107110
ZStack {
108111
RoundedRectangle(cornerRadius: 15)
109-
.stroke(.green, lineWidth: 5)
110-
112+
.stroke(boundingBoxVisibility == 1 ? .green : .red, lineWidth: 5)
113+
111114
RoundedRectangle(cornerRadius: 15)
112115
.fill(.green.opacity(0.5))
113116
.mask(alignment: .leading) {
@@ -117,7 +120,7 @@ struct QuickPoseBasicView: View {
117120
}
118121
.frame(width: geometry.size.width * 0.6, height: geometry.size.height * 0.8)
119122
.padding(.horizontal, (geometry.size.width * 1 - 0.6)/2)
120-
123+
121124
case .results(let results):
122125
WorkoutResultsView(sessionData: results)
123126
.environmentObject(viewModel)
@@ -176,89 +179,94 @@ struct QuickPoseBasicView: View {
176179
AVSpeechSynthesizer().speak(utterance)
177180
}
178181

179-
if state == .introBoundingBox {
180-
quickPose.start(features: sessionConfig.exercise.features, onFrame: { status, image, features, feedback, landmarks in
181-
overlayImage = image
182-
if case .success(_,_) = status {
183-
184-
switch state {
185-
case .introBoundingBox:
186-
187-
if let landmarks = landmarks, canMoveFromBoundingBox(landmarks: landmarks) {
182+
if case .results(let result) = state {
183+
let sessionDataDump = SessionDataModel(exercise: sessionConfig.exercise.name, count: result.count, seconds: result.seconds, date: Date())
184+
appendToJson(sessionData: sessionDataDump)
185+
}
186+
187+
quickPose.update(features: state.features ?? sessionConfig.exercise.features)
188+
}
189+
.onAppear() {
190+
UIApplication.shared.isIdleTimerDisabled = true
191+
quickPose.start(features: state.features ?? sessionConfig.exercise.features, onFrame: { status, image, features, feedback, landmarks in
192+
overlayImage = image
193+
if case .success(_,_) = status {
188194

189-
state = .boundingBox(enterTime: Date())
190-
boundingBoxMaskWidth = 0
191-
withAnimation(.easeInOut(duration: 2)) {
192-
boundingBoxMaskWidth = 1.0
193-
}
194-
}
195-
case .boundingBox(let enterDate):
196-
if let landmarks = landmarks, canMoveFromBoundingBox(landmarks: landmarks) {
197-
if -enterDate.timeIntervalSinceNow > 2 {
198-
state = .introExercise(sessionConfig.exercise)
195+
switch state {
196+
case .introBoundingBox:
197+
198+
if let result = features.first?.value, result.value == 1.0 {
199+
unchanged.reset()
200+
state = .boundingBox
201+
}
202+
case .boundingBox:
203+
if let dictionaryEntry = features.first {
204+
let result = dictionaryEntry.value
205+
boundingBoxVisibility = result.value
206+
unchanged.count(result: result.value) {
207+
if result.value == 1 { // been in for 2 seconds
208+
boundingBoxMaskWidth = 0
209+
withAnimation(.easeInOut(duration: 1)) {
210+
boundingBoxMaskWidth = 1.0
211+
}
212+
DispatchQueue.main.asyncAfter(deadline: .now()+1) {
213+
state = .introExercise(sessionConfig.exercise)
214+
}
215+
} else {
216+
state = .introBoundingBox
199217
}
200-
} else {
201-
state = .introBoundingBox
202218
}
219+
}
203220

204-
case .introExercise(_):
205-
DispatchQueue.main.asyncAfter(deadline: .now()+0.5) {
206-
state = .exercise(SessionData(count: 0, seconds: 0), enterTime: Date())
207-
}
208-
case .exercise(_, let enterDate):
209-
let secondsElapsed = Int(-enterDate.timeIntervalSinceNow)
210-
211-
if let feedback = feedback[.fitness(.bicepCurls)] {
212-
feedbackText = feedback.displayString
213-
} else {
214-
feedbackText = nil
215-
216-
if case .fitness = sessionConfig.exercise.features.first, let result = features[sessionConfig.exercise.features.first!] {
217-
_ = counter.count(result.value) { newState in
218-
if !newState.isEntered {
219-
DispatchQueue.main.asyncAfter(deadline: .now()+0.1) {
220-
withAnimation(.easeInOut(duration: 0.1)) {
221-
countScale = 2.0
222-
}
223-
DispatchQueue.main.asyncAfter(deadline: .now()+0.4) {
224-
withAnimation(.easeInOut(duration: 0.2)) {
225-
countScale = 1.0
226-
}
221+
case .introExercise(_):
222+
DispatchQueue.main.asyncAfter(deadline: .now()+0.5) {
223+
state = .exercise(SessionData(count: 0, seconds: 0), enterTime: Date())
224+
}
225+
case .exercise(_, let enterDate):
226+
let secondsElapsed = Int(-enterDate.timeIntervalSinceNow)
227+
228+
if let feedback = feedback[.fitness(.bicepCurls)] {
229+
feedbackText = feedback.displayString
230+
} else {
231+
feedbackText = nil
232+
233+
if case .fitness = sessionConfig.exercise.features.first, let result = features[sessionConfig.exercise.features.first!] {
234+
_ = counter.count(result.value) { newState in
235+
if !newState.isEntered {
236+
DispatchQueue.main.asyncAfter(deadline: .now()+0.1) {
237+
withAnimation(.easeInOut(duration: 0.1)) {
238+
countScale = 2.0
239+
}
240+
DispatchQueue.main.asyncAfter(deadline: .now()+0.4) {
241+
withAnimation(.easeInOut(duration: 0.2)) {
242+
countScale = 1.0
227243
}
228244
}
229245
}
230246
}
231247
}
232248
}
249+
}
233250

234-
let newResults = SessionData(count: counter.state.count, seconds: secondsElapsed)
235-
state = .exercise(newResults, enterTime: enterDate) // refresh view for every updated second
236-
var hasFinished = false
237-
if sessionConfig.useReps {
238-
hasFinished = counter.state.count >= sessionConfig.nReps
239-
} else {
240-
hasFinished = secondsElapsed >= sessionConfig.nSeconds + sessionConfig.nMinutes * 60
241-
}
251+
let newResults = SessionData(count: counter.state.count, seconds: secondsElapsed)
252+
state = .exercise(newResults, enterTime: enterDate) // refresh view for every updated second
253+
var hasFinished = false
254+
if sessionConfig.useReps {
255+
hasFinished = counter.state.count >= sessionConfig.nReps
256+
} else {
257+
hasFinished = secondsElapsed >= sessionConfig.nSeconds + sessionConfig.nMinutes * 60
258+
}
242259

243-
if hasFinished {
244-
state = .results(newResults)
245-
}
246-
default:
247-
break
260+
if hasFinished {
261+
state = .results(newResults)
248262
}
249-
} else {
250-
state = .introBoundingBox
263+
default:
264+
break
251265
}
252-
})
253-
}
254-
255-
if case .results(let result) = state {
256-
let sessionDataDump = SessionDataModel(exercise: sessionConfig.exercise.name, count: result.count, seconds: result.seconds, date: Date())
257-
appendToJson(sessionData: sessionDataDump)
258-
}
259-
}
260-
.onAppear() {
261-
UIApplication.shared.isIdleTimerDisabled = true
266+
} else if state != .startVolume && state != .instructions{
267+
state = .introBoundingBox
268+
}
269+
})
262270
}
263271
.onDisappear {
264272
quickPose.stop()

0 commit comments

Comments
 (0)