mirror of
https://github.com/Team4388/RidgeScout.git
synced 2026-06-09 00:37:59 -06:00
Compare commits
7 Commits
v2.0
...
qr-block-adjust
| Author | SHA1 | Date | |
|---|---|---|---|
| 99c3fa48dc | |||
| e6073ded49 | |||
| 7503cefa09 | |||
| 3b4737e6bc | |||
| 76d28e46cd | |||
| 73308f2acc | |||
| c18a93cb1a |
@@ -11,10 +11,10 @@
|
|||||||
|
|
||||||
#### Here is an overview of the main features currently included in the app:
|
#### Here is an overview of the main features currently included in the app:
|
||||||
- This project is written for Android! No need for some kind of janky laptop charging setup.
|
- This project is written for Android! No need for some kind of janky laptop charging setup.
|
||||||
- Similar to ScoutingPASS, there are many diffrent types of fields that can be used to collect data.
|
- Similar to ScoutingPASS, many different types of fields can be used to collect data.
|
||||||
- The app is designed to handle updates to the fields on the fly, without loosing any data!
|
- The app is designed to handle updates to the fields on the fly, without losing any data!
|
||||||
- Unlike other scouting solutions, scouters can disable any field they did not measure, and disabled fields will not be included in any calculations.
|
- Unlike other scouting solutions, scouters can disable any field they did not measure, and disabled fields will not be included in any calculations.
|
||||||
- Dynamic displays based off of the diffrent fields.
|
- Dynamic displays based on the different fields.
|
||||||
- Data transfer including 2D codes, Bluetooth, and File Bundle.
|
- Data transfer including 2D codes, Bluetooth, and File Bundle.
|
||||||
- Exporting using CSV.
|
- Exporting using CSV.
|
||||||
- Deployment on F-Droid
|
- Deployment on F-Droid
|
||||||
@@ -22,10 +22,9 @@
|
|||||||
|
|
||||||
#### Things that are yet to be implemented:
|
#### Things that are yet to be implemented:
|
||||||
- A page that lets users cross-compare scouting data between teams. (Compare)
|
- A page that lets users cross-compare scouting data between teams. (Compare)
|
||||||
- A page that lets scouters more easily make reports to the drive team before a match starts (Report)
|
|
||||||
|
|
||||||
#### Things that may or may not be implemented:
|
#### Things that may or may not be implemented:
|
||||||
- Statbotics intgration
|
- Statbotics integration
|
||||||
- Scout error estimation using OPR-like calculation
|
- Scout error estimation using OPR-like calculation
|
||||||
- - Would most likely require Statbotics
|
- - Would most likely require Statbotics
|
||||||
https://www.thebluealliance.com/avatars
|
https://www.thebluealliance.com/avatars
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ android {
|
|||||||
dependenciesInfo {
|
dependenciesInfo {
|
||||||
// Disables dependency metadata when building APKs.
|
// Disables dependency metadata when building APKs.
|
||||||
includeInApk = false
|
includeInApk = false
|
||||||
// Disables dependency metadata when building Android App Bundles.
|
// Disables dependency metadata when building Android App Bundles.5
|
||||||
includeInBundle = false
|
includeInBundle = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,8 +25,8 @@ android {
|
|||||||
applicationId = "com.ridgebotics.ridgescout"
|
applicationId = "com.ridgebotics.ridgescout"
|
||||||
minSdk = 24
|
minSdk = 24
|
||||||
targetSdk = 34
|
targetSdk = 34
|
||||||
versionCode = 11 // **IMPORTANT** Increment this before releasing on github
|
versionCode = 12 // **IMPORTANT** Increment this before releasing on github
|
||||||
versionName = "1.4"// **IMPORTANT** Change this before releasing on github (<Year num since 2024>.<Update Version>)
|
versionName = "2.0"// **IMPORTANT** Change this before releasing on github (<Year num since 2024>.<Update Version>)
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -157,9 +157,17 @@ public class ScoutingFragment extends Fragment {
|
|||||||
private void updateDashboard() {
|
private void updateDashboard() {
|
||||||
binding.textName.setText("Welcome, " + SettingsManager.getUsername() + "!");
|
binding.textName.setText("Welcome, " + SettingsManager.getUsername() + "!");
|
||||||
|
|
||||||
int curMatchNum = SettingsManager.getMatchNum();
|
binding.textRescoutIndicator.setText("Things to rescout: " + DataManager.rescout_list.size());
|
||||||
int nextMatch;
|
|
||||||
|
if(event.matches.size() == 0) {
|
||||||
|
binding.textMatchAlliance.setText("No Matches!");
|
||||||
|
} else {
|
||||||
int teamNum = SettingsManager.getTeamNum();
|
int teamNum = SettingsManager.getTeamNum();
|
||||||
|
int curMatchNum = SettingsManager.getMatchNum();
|
||||||
|
|
||||||
|
binding.textMatchAlliance.setText("Match: " + (curMatchNum+1) + ", " + SettingsManager.getAllyPos());
|
||||||
|
|
||||||
|
int nextMatch;
|
||||||
try {
|
try {
|
||||||
nextMatch = event.getNextTeamMatch(teamNum, curMatchNum).matchIndex;
|
nextMatch = event.getNextTeamMatch(teamNum, curMatchNum).matchIndex;
|
||||||
} catch (Exception e){
|
} catch (Exception e){
|
||||||
@@ -167,8 +175,6 @@ public class ScoutingFragment extends Fragment {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.textMatchAlliance.setText("Match: " + (curMatchNum+1) + ", " + SettingsManager.getAllyPos());
|
|
||||||
binding.textRescoutIndicator.setText("Things to rescout: " + DataManager.rescout_list.size());
|
|
||||||
|
|
||||||
binding.infoBox.addView(new TextViewBuilder(getContext(), "Our next match: Match " + nextMatch)
|
binding.infoBox.addView(new TextViewBuilder(getContext(), "Our next match: Match " + nextMatch)
|
||||||
.body1()
|
.body1()
|
||||||
@@ -182,3 +188,4 @@ public class ScoutingFragment extends Fragment {
|
|||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@@ -165,12 +165,12 @@ public class TBASelectorFragment extends Fragment {
|
|||||||
try {
|
try {
|
||||||
Date startDate = format.parse(j.getString("start_date"));
|
Date startDate = format.parse(j.getString("start_date"));
|
||||||
Date endDate = format.parse(j.getString("end_date"));
|
Date endDate = format.parse(j.getString("end_date"));
|
||||||
if(currentTime.after(endDate)){
|
if(currentTime.after(startDate) && currentTime.before(endDate)) {
|
||||||
|
row.setColor(tba_current);
|
||||||
|
} else if(currentTime.after(endDate)){
|
||||||
row.setColor(tba_previous);
|
row.setColor(tba_previous);
|
||||||
}else if(currentTime.before(startDate)){
|
}else if(currentTime.before(startDate)){
|
||||||
row.setColor(tba_next);
|
row.setColor(tba_next);
|
||||||
}else if(currentTime.after(startDate) && currentTime.before(endDate)){
|
|
||||||
row.setColor(tba_current);
|
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
AlertManager.error("Failed finding start and end dates!", e);
|
AlertManager.error("Failed finding start and end dates!", e);
|
||||||
|
|||||||
+114
-1
@@ -87,6 +87,10 @@ public class CodeScannerView extends Fragment {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final int BLOCK_SIZE = 32; // Size of each block in pixels
|
||||||
|
|
||||||
|
|
||||||
private Bitmap toGreyscale(Image image){
|
private Bitmap toGreyscale(Image image){
|
||||||
// Turns out the "Y" In YUV is the Luminance of the pixel.
|
// Turns out the "Y" In YUV is the Luminance of the pixel.
|
||||||
// Makes converting to greyscale 1000x easier
|
// Makes converting to greyscale 1000x easier
|
||||||
@@ -94,12 +98,17 @@ public class CodeScannerView extends Fragment {
|
|||||||
final int width = image.getWidth();
|
final int width = image.getWidth();
|
||||||
final int height = image.getHeight();
|
final int height = image.getHeight();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
int[] pixels = new int[width * height];
|
int[] pixels = new int[width * height];
|
||||||
for (int i = 0; i < width*height; i++) {
|
for (int i = 0; i < width*height; i++) {
|
||||||
int L = levelMap[yBuffer.get(i) & 0xff];
|
// int L = levelMap[yBuffer.get(i) & 0xff];
|
||||||
|
int L = yBuffer.get(i) & 0xff;
|
||||||
pixels[i] = 0xff000000 | (L << 16) | (L << 8) | L;
|
pixels[i] = 0xff000000 | (L << 16) | (L << 8) | L;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
threshold(pixels, width, height);
|
||||||
|
|
||||||
Matrix matrix = new Matrix();
|
Matrix matrix = new Matrix();
|
||||||
|
|
||||||
matrix.postRotate(90);
|
matrix.postRotate(90);
|
||||||
@@ -112,6 +121,110 @@ public class CodeScannerView extends Fragment {
|
|||||||
|
|
||||||
return bitmap;
|
return bitmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs mean block binarization.
|
||||||
|
* * Note: The function name 'toGreyscale' is kept per your request,
|
||||||
|
* but this method actually performs binarization (Black/White).
|
||||||
|
*
|
||||||
|
* @param image The array of pixels (ARGB format, standard in Android)
|
||||||
|
* @param width The width of the image
|
||||||
|
* @param height The height of the image
|
||||||
|
*/
|
||||||
|
private void threshold(int[] image, int width, int height) {
|
||||||
|
|
||||||
|
// 1. Setup Block Grid Dimensions
|
||||||
|
// We use Math.ceil to ensure partial blocks at the edges are counted
|
||||||
|
int gridCols = (int) Math.ceil((double) width / BLOCK_SIZE);
|
||||||
|
int gridRows = (int) Math.ceil((double) height / BLOCK_SIZE);
|
||||||
|
|
||||||
|
// Arrays to store statistics for each block
|
||||||
|
int[] blockSums = new int[gridCols * gridRows];
|
||||||
|
int[] blockCounts = new int[gridCols * gridRows];
|
||||||
|
int[] blockMeans = new int[gridCols * gridRows];
|
||||||
|
|
||||||
|
// --- PASS 1: Compute Block Statistics (Mean Intensity) ---
|
||||||
|
for (int y = 0; y < height; y++) {
|
||||||
|
int blockY = y / BLOCK_SIZE;
|
||||||
|
|
||||||
|
for (int x = 0; x < width; x++) {
|
||||||
|
int blockX = x / BLOCK_SIZE;
|
||||||
|
int blockIndex = blockY * gridCols + blockX;
|
||||||
|
|
||||||
|
// Extract average grayscale value from ARGB pixel
|
||||||
|
int pixel = image[y * width + x];
|
||||||
|
int r = (pixel >> 16) & 0xFF;
|
||||||
|
int g = (pixel >> 8) & 0xFF;
|
||||||
|
int b = pixel & 0xFF;
|
||||||
|
// Simple average (matches BoofCV's typical approach for generic buffers)
|
||||||
|
int gray = (r + g + b) / 3;
|
||||||
|
|
||||||
|
blockSums[blockIndex] += gray;
|
||||||
|
blockCounts[blockIndex]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the mean for every block
|
||||||
|
for (int i = 0; i < blockSums.length; i++) {
|
||||||
|
if (blockCounts[i] > 0) {
|
||||||
|
blockMeans[i] = blockSums[i] / blockCounts[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- PASS 2: Apply Threshold using Local 3x3 Block Region ---
|
||||||
|
for (int blockY = 0; blockY < gridRows; blockY++) {
|
||||||
|
for (int blockX = 0; blockX < gridCols; blockX++) {
|
||||||
|
|
||||||
|
// Calculate the threshold for this specific block
|
||||||
|
// by averaging the means of the surrounding 3x3 blocks.
|
||||||
|
// This corresponds to BoofCV's `thresholdFromLocalBlocks` logic.
|
||||||
|
long localSum = 0;
|
||||||
|
int localCount = 0;
|
||||||
|
|
||||||
|
int startGridY = Math.max(0, blockY - 1);
|
||||||
|
int endGridY = Math.min(gridRows - 1, blockY + 1);
|
||||||
|
int startGridX = Math.max(0, blockX - 1);
|
||||||
|
int endGridX = Math.min(gridCols - 1, blockX + 1);
|
||||||
|
|
||||||
|
for (int ny = startGridY; ny <= endGridY; ny++) {
|
||||||
|
for (int nx = startGridX; nx <= endGridX; nx++) {
|
||||||
|
localSum += blockMeans[ny * gridCols + nx];
|
||||||
|
localCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int threshold = (localCount > 0) ? (int) (localSum / localCount) : 127;
|
||||||
|
|
||||||
|
// Apply this threshold to all pixels within the current block
|
||||||
|
int startPixelX = blockX * BLOCK_SIZE;
|
||||||
|
int startPixelY = blockY * BLOCK_SIZE;
|
||||||
|
// Handle image boundary (if image size isn't perfectly divisible by block size)
|
||||||
|
int endPixelX = Math.min(startPixelX + BLOCK_SIZE, width);
|
||||||
|
int endPixelY = Math.min(startPixelY + BLOCK_SIZE, height);
|
||||||
|
|
||||||
|
for (int y = startPixelY; y < endPixelY; y++) {
|
||||||
|
for (int x = startPixelX; x < endPixelX; x++) {
|
||||||
|
int index = y * width + x;
|
||||||
|
|
||||||
|
// Recalculate gray to compare against threshold
|
||||||
|
int pixel = image[index];
|
||||||
|
int r = (pixel >> 16) & 0xFF;
|
||||||
|
int g = (pixel >> 8) & 0xFF;
|
||||||
|
int b = pixel & 0xFF;
|
||||||
|
int gray = (r + g + b) / 3;
|
||||||
|
|
||||||
|
// Binarize: Black if <= threshold, White if > threshold
|
||||||
|
if (gray <= threshold) {
|
||||||
|
image[index] = 0xFF000000; // Black (Alpha 255)
|
||||||
|
} else {
|
||||||
|
image[index] = 0xFFFFFFFF; // White (Alpha 255)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void scanQRCode(Bitmap bitmap) {
|
public void scanQRCode(Bitmap bitmap) {
|
||||||
|
|
||||||
// CodeScanTask async = new CodeScanTask();
|
// CodeScanTask async = new CodeScanTask();
|
||||||
|
|||||||
@@ -67,9 +67,9 @@ public class Colors {
|
|||||||
public static final int fileselector_unselected_color = 0x50006600;
|
public static final int fileselector_unselected_color = 0x50006600;
|
||||||
|
|
||||||
// TBA
|
// TBA
|
||||||
public static final int tba_previous = 0x30FF0000;
|
public static final int tba_previous = Color.argb(30, 64,64,64);
|
||||||
public static final int tba_current = 0x50ff0000;
|
public static final int tba_current = 0x7f00ff00;
|
||||||
public static final int tba_next = 0x30FFFF00;
|
public static final int tba_next = Color.argb(30, 192,192,192);
|
||||||
public static final int tba_red = 0x50ff0000;
|
public static final int tba_red = 0x50ff0000;
|
||||||
public static final int tba_blue = 0x500000ff;
|
public static final int tba_blue = 0x500000ff;
|
||||||
public static final int tba_toggle_background = 0x30000000;
|
public static final int tba_toggle_background = 0x30000000;
|
||||||
|
|||||||
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 493 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 158 KiB After Width: | Height: | Size: 434 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 162 KiB After Width: | Height: | Size: 454 KiB |
Reference in New Issue
Block a user