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

main <- dev #420

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
747e65d
Fixes #369. Selected trial is selected by default in kinematics dashb…
AlbertoCasasOrtiz Dec 10, 2024
17b6765
Fix #389. Button on neutral page dissapears when processing.
AlbertoCasasOrtiz Dec 10, 2024
791e35d
support for checkerboard lying on the floor
antoinefalisse Dec 12, 2024
26e75ed
Merge pull request #413 from stanfordnmbl/flat_checkerboard
AlbertoCasasOrtiz Dec 17, 2024
1f69440
Checking if devices connected before changing to recording status.
AlbertoCasasOrtiz Jan 7, 2025
e826b8f
Merge pull request #416 from stanfordnmbl/fix-start-recording-issue
AlbertoCasasOrtiz Jan 7, 2025
67523aa
Merge pull request #411 from stanfordnmbl/fix-trial-selected-kinemati…
AlbertoCasasOrtiz Jan 7, 2025
4c15b40
Properly canceling and erroring trial when any of the cameras are not…
AlbertoCasasOrtiz Jan 8, 2025
3b7719d
Merge pull request #417 from stanfordnmbl/fix-start-recording-issue
AlbertoCasasOrtiz Jan 8, 2025
398b0a0
Added sound effects when neutral finished and when recording started.
AlbertoCasasOrtiz Jan 8, 2025
098b648
Merge pull request #419 from stanfordnmbl/sound-effects
AlbertoCasasOrtiz Jan 8, 2025
c1a145d
Merge pull request #421 from stanfordnmbl/main
AlbertoCasasOrtiz Jan 9, 2025
e292637
Added sound effects for calibration finished, neutral finished, recor…
AlbertoCasasOrtiz Jan 9, 2025
fb5ad2c
Merge pull request #422 from stanfordnmbl/sound-effects
AlbertoCasasOrtiz Jan 9, 2025
83ef503
Auditory feedback is optional now. There is a checkbox in settings to…
AlbertoCasasOrtiz Jan 10, 2025
9247974
Merge pull request #423 from stanfordnmbl/sound-effects
AlbertoCasasOrtiz Jan 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added public/sounds/calibration_finished.ogg
Binary file not shown.
Binary file added public/sounds/neutral_finished.ogg
Binary file not shown.
Binary file added public/sounds/recording.ogg
Binary file not shown.
Binary file added public/sounds/recording_finished.ogg
Binary file not shown.
Binary file added public/sounds/success.ogg
Binary file not shown.
62 changes: 55 additions & 7 deletions src/components/pages/Calibration.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,13 @@

<v-card-text class="d-flex align-center">
<ul class="flex-grow-1">
<li>It should be visible by all cameras (nothing in the way of cameras' view when hitting Calibrate)</li>
<li>It should be horizontal (longer side on the floor)</li>
<li>It should be perpendicular to the floor (not lying on the floor)</li>
<li>It should be visible by all cameras (nothing in the way of cameras' view when hitting Calibrate)</li>
<li>It can be either perpendicular to the floor (default) or lying on the floor (beta feature; select Lying placement below)</li>
<li>If perpendicular to the floor, then:
<ul>
<li>Place it horizontally (longer side on the floor)</li>
</ul>
</li>
</ul>

<div class="image-container pa-3">
Expand Down Expand Up @@ -51,7 +55,38 @@

<v-text-field
v-model="squareSize"
label="Square size (mm)"/>
label="Square size (mm)"
class="mr-3"/>

<v-select
v-model="placement"
:items="['Perpendicular', 'Lying']"
label="Placement on the floor"
class="mr-0"/>

<v-tooltip bottom="" max-width="500px">
<template v-slot:activator="{ on }">
<v-icon v-on="on" class="ml-0">mdi-help-circle-outline</v-icon>
</template>
<div>
The origin of the world frame is the top-left black-to-black corner of the board (red dot with a blue outline in the picture on the right).
<br><br>
When positioned perpendicular to the floor, transformations are applied so that in the processed data:
<ul>
<li>The forward axis of the world frame is perpendicular to the board (coming out).</li>
<li>The vertical axis of the world frame is parallel to the board (going up).</li>
</ul>
<br>
When positioned lying on the floor, transformations are applied so that in the processed data:
<ul>
<li>The forward axis of the world frame is parallel to the board (along the shorter side).</li>
<li>The vertical axis of the world frame is perpendicular to the board (going up).</li>
</ul>
<br>
To align movement with the forward axis of the world frame when the board is lying on the floor, place the board such that its forward axis is parallel to the direction of movement.
For example, for walking, place the board with the longer side perpendicular to the walking direction. Note that this alignment is optional, as the system can operate with the board in any orientation.
</div>
</v-tooltip>
</div>

<div class="image-container pa-3">
Expand All @@ -68,6 +103,7 @@ import axios from 'axios'
import {mapActions, mapMutations, mapState} from 'vuex'
import { apiError, apiSuccess, apiErrorRes, apiInfo} from '@/util/ErrorMessage.js'
import MainLayout from '@/layout/MainLayout'
import { playCalibrationFinishedSound } from "@/util/SoundMessage.js";

export default {
name: 'Calibration',
Expand All @@ -79,13 +115,20 @@ export default {
rows: 4,
cols: 5,
squareSize: 35,
placement: 'Perpendicular',
busy: false,
imgs: null,
lastPolledStatus: "",
n_cameras_connected: 0,
n_videos_uploaded: 0
n_videos_uploaded: 0,
isAuditoryFeedbackEnabled: false,
}
},
created() {
// Load the initial value from localStorage
const storedValue = localStorage.getItem("auditory_feedback");
this.isAuditoryFeedbackEnabled = storedValue === "true";
},
computed: {
...mapState({
session: state => state.data.session,
Expand All @@ -108,15 +151,16 @@ export default {
this.setCalibration({
rows: this.rows,
cols: this.cols,
squareSize: this.squareSize
squareSize: this.squareSize,
placement: this.placement
})
try {
const resUpdate = await axios.get(`/sessions/${this.session.id}/set_metadata/`, {
params: {
cb_rows: this.rows,
cb_cols: this.cols,
cb_square: this.squareSize,
cb_placement: "backWall"
cb_placement: this.placement
}
})

Expand Down Expand Up @@ -147,6 +191,10 @@ export default {
apiError(this.n_calibrated_cameras + " device(s) connected to the session and 2+ devices are required, please re-pair your devices using qr code at top of page.", 10000);
this.busy = false
} else {
// Play sound indicating calibration finished.
if (this.isAuditoryFeedbackEnabled)
playCalibrationFinishedSound();

apiSuccess(this.n_calibrated_cameras + " devices calibrated successfully.", 5000);
this.$router.push(`/${this.session.id}/neutral`)
}
Expand Down
54 changes: 31 additions & 23 deletions src/components/pages/Dashboard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -189,20 +189,7 @@ export default {
},
// This function is executed once the page has been loaded.
created: function () {
// Indicates if the current logged in user owns the session.
this.session_owned = false

// If the user is logged in, select session from list of sessions.
if(this.loggedIn) {
// If a session id has been passed as a parameter, set it as the default session.
this.sessionsIds.forEach(sessionId => {
if (sessionId.includes(this.$route.params.id)) {
this.session_selected = sessionId;
this.onSessionSelected(this.session_selected);
this.session_owned = true
}
});
}
},
methods: {
...mapActions('data', ['loadSession']),
Expand Down Expand Up @@ -521,6 +508,7 @@ export default {
return {
current_session_id: "",
session_selected: "",
session_owned: false,
trial_selected: "",
trial_names: [],
trial_ids: [],
Expand Down Expand Up @@ -651,7 +639,6 @@ export default {
return value !== "";
});
return filtered_sessions;

},
sessionsIds() {
var result_sessions = this.sessions.map(function (obj) {
Expand All @@ -677,14 +664,32 @@ export default {
}
},
async mounted () {
// Set session as current session.
this.current_session_id = this.$route.params.id;
// Show spinner and hide chart until finished.
document.getElementById("spinner-layer").style.display = "block";
document.getElementById("chart").style.display = "None";
// Set session as current session.
this.current_session_id = this.$route.params.id;

// If not logged in, load session from params and show trials.
await this.loadSession(this.$route.params.id)
// First check ownership.
// Indicates if the current logged in user owns the session.
this.session_owned = false

// If the user is logged in, select session from list of sessions.
if(this.loggedIn) {
// If a session id has been passed as a parameter, set it as the default session.
this.sessionsIds.forEach(sessionId => {
if (sessionId.includes(this.current_session_id)) {
this.session_selected = sessionId;
this.onSessionSelected(this.session_selected);
this.session_owned = true
}
});
}

// Show spinner and hide chart until finished.
document.getElementById("spinner-layer").style.display = "block";
document.getElementById("chart").style.display = "None";

// If not logged in, load session from params and show trials.
if (this.$route.params.id)
await this.loadSession(this.$route.params.id)

var trials = this.session['trials'];
// Filter trials by name.
Expand All @@ -696,8 +701,11 @@ export default {
this.trial_names.push(element.name);
this.trial_ids.push(element.id)
});
this.trial_selected = this.trial_names[0];

if (this.$route.params.trialId && this.trial_names.includes(this.$route.params.trialId)){
this.trial_selected = this.$route.params.trialId;
} else {
this.trial_selected = this.trial_names[0];
}
// Load data from this trial.
this.onTrialSelected(this.trial_selected);

Expand Down
12 changes: 12 additions & 0 deletions src/components/pages/Neutral.vue
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,7 @@
import axios from "axios";
import { mapMutations, mapActions, mapState } from "vuex";
import { apiError, apiSuccess, apiErrorRes, apiWarning, apiInfo, clearToastMessages } from "@/util/ErrorMessage.js";
import { playNeutralFinishedSound } from "@/util/SoundMessage.js";
import MainLayout from "@/layout/MainLayout";
import ExampleImage from "@/components/ui/ExampleImage";
import DialogComponent from '@/components/ui/SubjectDialog.vue'
Expand Down Expand Up @@ -450,6 +451,7 @@ export default {
done: "Confirm",
error: "Re-record",
stopped: "Processing",
processing: "Processing",
},
checkboxRule: (v) => !!v || 'The subject must agree to continue!',

Expand All @@ -459,8 +461,15 @@ export default {

tempFilterFrequency: 'default', // Temporary input holder
componentKey: 0,

isAuditoryFeedbackEnabled: false,
};
},
created() {
// Load the initial value from localStorage
const storedValue = localStorage.getItem("auditory_feedback");
this.isAuditoryFeedbackEnabled = storedValue === "true";
},
computed: {
...mapState({
// subjects: (state) => state.data.subjects,
Expand Down Expand Up @@ -832,6 +841,9 @@ export default {
) {
clearToastMessages();
apiInfo("Processing: the subject can relax.", 5000);

if (this.isAuditoryFeedbackEnabled)
playNeutralFinishedSound()
}
this.lastPolledStatus = res.data.status;
window.setTimeout(this.pollStatus, 1000);
Expand Down
27 changes: 26 additions & 1 deletion src/components/pages/ProfilePage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,21 @@

<div v-else-if="editing_settings" class="row">
<v-col align="center" justify="center" class="mt-8">

<v-row align="center" justify="center">
<p>
<label>
<input
type="checkbox"
v-model="isAuditoryFeedbackEnabled"
@change="updateLocalStorage"
/>
Enable Voice Auditory Feedback (audio updates for start and completion events).
</label>

</p>
</v-row>

<v-row align="center" justify="center">
<p>
Remove your account and all associated data. This includes every session, trial, subject, and result that you have ever created. This process is irreversible.
Expand Down Expand Up @@ -437,9 +452,15 @@ export default {
profile_picture: null,
selectedImageFile: null,
current_user_page_profile_url: '',
confirm_username: ''
confirm_username: '',
isAuditoryFeedbackEnabled: false,
};
},
created() {
// Load the initial value from localStorage
const storedValue = localStorage.getItem("auditory_feedback");
this.isAuditoryFeedbackEnabled = storedValue === "true";
},
methods: {
...mapActions("auth", ["updateProfile", "updateProfilePicture", "set_profile_picture_url", "logout"]),
handleShareProfileClick() {
Expand All @@ -448,6 +469,10 @@ export default {
handleEditProfile() {
this.editing_profile = true;
},
updateLocalStorage() {
// Update localStorage when the checkbox changes
localStorage.setItem("auditory_feedback", this.isAuditoryFeedbackEnabled);
},
handleEditSettings() {
this.editing_settings = true;
},
Expand Down
Loading