Skip to content

Commit

Permalink
Merge branch 'master' into cameleon-removal
Browse files Browse the repository at this point in the history
  • Loading branch information
srimanachanta committed Oct 17, 2023
2 parents c779840 + ededc4f commit f003660
Show file tree
Hide file tree
Showing 10 changed files with 191 additions and 74 deletions.
74 changes: 62 additions & 12 deletions photon-client/src/components/app/photon-camera-stream.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
import { useStateStore } from "@/stores/StateStore";
import loadingImage from "@/assets/images/loading.svg";
import type { StyleValue } from "vue/types/jsx";
import CvIcon from "@/components/common/cv-icon.vue";
const props = defineProps<{
streamType: "Raw" | "Processed";
id?: string;
}>();
const src = computed<string>(() => {
const streamSrc = computed<string>(() => {
const port =
useCameraSettingsStore().currentCameraSettings.stream[props.streamType === "Raw" ? "inputPort" : "outputPort"];
Expand All @@ -20,25 +21,74 @@ const src = computed<string>(() => {
return `http://${inject("backendHostname")}:${port}/stream.mjpg`;
});
const alt = computed<string>(() => `${props.streamType} Stream View`);
const style = computed<StyleValue>(() => {
const streamDesc = computed<string>(() => `${props.streamType} Stream View`);
const streamStyle = computed<StyleValue>(() => {
if (useStateStore().colorPickingMode) {
return { cursor: "crosshair" };
} else if (src.value !== loadingImage) {
return { cursor: "pointer" };
return { width: "100%", cursor: "crosshair" };
} else if (streamSrc.value !== loadingImage) {
return { width: "100%", cursor: "pointer" };
}
return {};
return { width: "100%" };
});
const handleClick = () => {
if (!useStateStore().colorPickingMode && src.value !== loadingImage) {
window.open(src.value);
const overlayStyle = computed<StyleValue>(() => {
if (useStateStore().colorPickingMode || streamSrc.value == loadingImage) {
return { display: "none" };
} else {
return {};
}
});
const handleStreamClick = () => {
if (!useStateStore().colorPickingMode && streamSrc.value !== loadingImage) {
window.open(streamSrc.value);
}
};
const handleCaptureClick = () => {
if (props.streamType === "Raw") {
useCameraSettingsStore().saveInputSnapshot();
} else {
useCameraSettingsStore().saveOutputSnapshot();
}
};
</script>

<template>
<img :id="id" crossorigin="anonymous" :src="src" :alt="alt" :style="style" @click="handleClick" />
<div class="stream-container">
<img
:id="id"
crossorigin="anonymous"
:src="streamSrc"
:alt="streamDesc"
:style="streamStyle"
@click="handleStreamClick"
/>
<div class="stream-overlay" :style="overlayStyle">
<cv-icon
icon-name="mdi-camera-image"
tooltip="Capture and save a frame of this stream"
class="ma-1 mr-2"
@click="handleCaptureClick"
/>
</div>
</div>
</template>

<style scoped>
.stream-container {
position: relative;
}
.stream-overlay {
opacity: 0;
transition: 0.1s ease;
position: absolute;
top: 0;
right: 0;
}
.stream-container:hover .stream-overlay {
opacity: 1;
}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,7 @@ const endCalibration = () => {
color="secondary"
style="width: 100%"
:disabled="!settingsValid"
@click="isCalibrating ? useCameraSettingsStore().takeCalibrationSnapshot(true) : startCalibration()"
@click="isCalibrating ? useCameraSettingsStore().takeCalibrationSnapshot() : startCalibration()"
>
{{ isCalibrating ? "Take Snapshot" : "Start Calibration" }}
</v-btn>
Expand Down
6 changes: 3 additions & 3 deletions photon-client/src/components/cameras/CamerasView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const fpsTooLow = computed<boolean>(() => {
</script>

<template>
<v-card class="mb-3 pr-6 pb-3 pa-4" color="primary" dark>
<v-card class="mb-3 pb-3 pa-4" color="primary" dark>
<v-card-title
class="pb-0 mb-2 pl-4 pt-1"
style="min-height: 50px; justify-content: space-between; align-content: center"
Expand Down Expand Up @@ -133,16 +133,16 @@ th {
.stream {
display: flex;
justify-content: center;
width: 100%;
}

@media only screen and (min-width: 512px) and (max-width: 960px) {
.stream-container {
flex-wrap: nowrap;
justify-content: center;
}

.stream {
width: 50%;
max-width: 50%;
}
}
</style>
28 changes: 14 additions & 14 deletions photon-client/src/components/settings/DeviceControlCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -63,19 +63,18 @@ const offlineUpdate = ref();
const openOfflineUpdatePrompt = () => {
offlineUpdate.value.click();
};
const handleOfflineUpdate = (payload: Event) => {
const handleOfflineUpdate = (payload: Event & { target: (EventTarget & HTMLInputElement) | null }) => {
if (payload.target === null || !payload.target.files) return;
const formData = new FormData();
formData.append("jarData", payload.target.files[0]);
useStateStore().showSnackbarMessage({
message: "New Software Upload in Progress...",
color: "secondary",
timeout: -1
});
const formData = new FormData();
if (payload.target == null || !payload.target?.files) return;
const files: FileList = payload.target.files as FileList;
formData.append("jarData", files[0]);
axios
.post("/utils/offlineUpdate", formData, {
headers: { "Content-Type": "multipart/form-data" },
Expand Down Expand Up @@ -138,21 +137,17 @@ enum ImportType {
HardwareSettings,
NetworkConfig
}
const showImportDialog = ref(false);
const importType = ref<ImportType | number>(-1);
const importFile = ref(null);
const importFile = ref<File | null>(null);
const handleSettingsImport = () => {
if (importType.value === -1 || importFile.value === null) return;
const formData = new FormData();
formData.append("data", importFile.value);
let settingsEndpoint;
let settingsEndpoint: string;
switch (importType.value) {
case ImportType.AllSettings:
settingsEndpoint = "";
break;
case ImportType.HardwareConfig:
settingsEndpoint = "/hardwareConfig";
break;
Expand All @@ -162,6 +157,10 @@ const handleSettingsImport = () => {
case ImportType.NetworkConfig:
settingsEndpoint = "/networkConfig";
break;
default:
case ImportType.AllSettings:
settingsEndpoint = "";
break;
}
axios
Expand Down Expand Up @@ -252,14 +251,15 @@ const handleSettingsImport = () => {
tooltip="Select the type of settings file you are trying to upload"
:items="['All Settings', 'Hardware Config', 'Hardware Settings', 'Network Config']"
:select-cols="10"
style="width: 100%"
/>
</v-row>
<v-row class="mt-6 ml-4 mr-8">
<v-file-input
v-model="importFile"
:disabled="importType === -1"
:error-messages="importType === -1 ? 'Settings type not selected' : ''"
:accept="importType === ImportType.AllSettings ? '.zip' : '.json'"
@change="(file) => (importFile = file)"
/>
</v-row>
<v-row
Expand Down
29 changes: 26 additions & 3 deletions photon-client/src/stores/settings/CameraSettingsStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -357,12 +357,35 @@ export const useCameraSettingsStore = defineStore("cameraSettings", {
/**
* Take a snapshot for the calibration processes
*
* @param takeSnapshot whether or not to take a snapshot. Defaults to true
* @param cameraIndex the index of the camera that is currently in the calibration process
*/
takeCalibrationSnapshot(takeSnapshot = true, cameraIndex: number = useStateStore().currentCameraIndex) {
takeCalibrationSnapshot(cameraIndex: number = useStateStore().currentCameraIndex) {
const payload = {
takeCalibrationSnapshot: takeSnapshot,
takeCalibrationSnapshot: true,
cameraIndex: cameraIndex
};
useStateStore().websocket?.send(payload, true);
},
/**
* Save a snapshot of the input frame of the camera.
*
* @param cameraIndex the index of the camera
*/
saveInputSnapshot(cameraIndex: number = useStateStore().currentCameraIndex) {
const payload = {
saveInputSnapshot: true,
cameraIndex: cameraIndex
};
useStateStore().websocket?.send(payload, true);
},
/**
* Save a snapshot of the output frame of the camera.
*
* @param cameraIndex the index of the camera
*/
saveOutputSnapshot(cameraIndex: number = useStateStore().currentCameraIndex) {
const payload = {
saveOutputSnapshot: true,
cameraIndex: cameraIndex
};
useStateStore().websocket?.send(payload, true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,58 +32,57 @@
import org.photonvision.vision.opencv.CVMat;

public class FileSaveFrameConsumer implements Consumer<CVMat> {
private final Logger logger = new Logger(FileSaveFrameConsumer.class, LogGroup.General);

// Formatters to generate unique, timestamped file names
private static final String FILE_PATH = ConfigManager.getInstance().getImageSavePath().toString();
private static final String FILE_EXTENSION = ".jpg";
private static final String NT_SUFFIX = "SaveImgCmd";

DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
DateFormat tf = new SimpleDateFormat("hhmmssSS");
private final String NT_SUFFIX = "SaveImgCmd";
private final String ntEntryName;
private NetworkTable subTable;

private final NetworkTable rootTable;
private final Logger logger;
private long imgSaveCountInternal = 0;
private String camNickname;
private final String fnamePrefix;
private IntegerEntry entry;
private NetworkTable subTable;
private final String ntEntryName;
private IntegerEntry saveFrameEntry;

private String cameraNickname;
private final String streamType;

private long savedImagesCount = 0;

public FileSaveFrameConsumer(String camNickname, String streamPrefix) {
this.fnamePrefix = camNickname + "_" + streamPrefix;
this.ntEntryName = streamPrefix + NT_SUFFIX;
this.cameraNickname = camNickname;
this.streamType = streamPrefix;

this.rootTable = NetworkTablesManager.getInstance().kRootTable;
updateCameraNickname(camNickname);
this.logger = new Logger(FileSaveFrameConsumer.class, this.camNickname, LogGroup.General);
}

public void accept(CVMat image) {
if (image != null && image.getMat() != null && !image.getMat().empty()) {
var curCommand = entry.get(); // default to just our current count
if (curCommand >= 0) {
// Only do something if we got a valid current command
if (imgSaveCountInternal < curCommand) {
// Save one frame.
// Create the filename
Date now = new Date();
String savefile =
FILE_PATH
+ File.separator
+ fnamePrefix
+ "_"
+ df.format(now)
+ "T"
+ tf.format(now)
+ FILE_EXTENSION;

// write to file
Imgcodecs.imwrite(savefile, image.getMat());

// Count one more image saved
imgSaveCountInternal++;
logger.info("Saved new image at " + savefile);

} else if (imgSaveCountInternal > curCommand) {
imgSaveCountInternal = curCommand;
}
long currentCount = saveFrameEntry.get();

// Await save request
if (currentCount == -1) return;

// The requested count is greater than the actual count
if (savedImagesCount < currentCount) {
Date now = new Date();

String fileName =
cameraNickname + "_" + streamType + "_" + df.format(now) + "T" + tf.format(now);
String saveFilePath = FILE_PATH + File.separator + fileName + FILE_EXTENSION;

Imgcodecs.imwrite(saveFilePath, image.getMat());

savedImagesCount++;
logger.info("Saved new image at " + saveFilePath);
} else if (savedImagesCount > currentCount) {
// Reset local value with NT value in case of de-sync
savedImagesCount = currentCount;
}
}
}
Expand All @@ -97,9 +96,14 @@ public void updateCameraNickname(String newCameraNickname) {
}

// Recreate and re-init network tables structure
this.camNickname = newCameraNickname;
this.subTable = rootTable.getSubTable(this.camNickname);
this.subTable.getEntry(ntEntryName).setInteger(imgSaveCountInternal);
this.entry = subTable.getIntegerTopic(ntEntryName).getEntry(-1); // Default negative
this.cameraNickname = newCameraNickname;
this.subTable = rootTable.getSubTable(this.cameraNickname);
this.subTable.getEntry(ntEntryName).setInteger(savedImagesCount);
this.saveFrameEntry = subTable.getIntegerTopic(ntEntryName).getEntry(-1); // Default negative
}

public void overrideTakeSnapshot() {
// Simulate NT change
saveFrameEntry.set(saveFrameEntry.get() + 1);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,14 @@ public void startCalibration(UICalibrationData data) {
setPipeline(PipelineManager.CAL_3D_INDEX);
}

public void saveInputSnapshot() {
inputFrameSaver.overrideTakeSnapshot();
}

public void saveOutputSnapshot() {
outputFrameSaver.overrideTakeSnapshot();
}

public void takeCalibrationSnapshot() {
pipelineManager.calibration3dPipeline.takeSnapshot();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,12 @@ public void onDataChangeEvent(DataChangeEvent<?> event) {
parentModule.startCalibration(data);
parentModule.saveAndBroadcastAll();
return;
case "saveInputSnapshot":
parentModule.saveInputSnapshot();
return;
case "saveOutputSnapshot":
parentModule.saveOutputSnapshot();
return;
case "takeCalSnapshot":
parentModule.takeCalibrationSnapshot();
return;
Expand Down
Loading

0 comments on commit f003660

Please sign in to comment.