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:
|
||||
- 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.
|
||||
- The app is designed to handle updates to the fields on the fly, without loosing any 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 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.
|
||||
- Dynamic displays based off of the diffrent fields.
|
||||
- Dynamic displays based on the different fields.
|
||||
- Data transfer including 2D codes, Bluetooth, and File Bundle.
|
||||
- Exporting using CSV.
|
||||
- Deployment on F-Droid
|
||||
@@ -22,14 +22,13 @@
|
||||
|
||||
#### Things that are yet to be implemented:
|
||||
- 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:
|
||||
- Statbotics intgration
|
||||
- Statbotics integration
|
||||
- Scout error estimation using OPR-like calculation
|
||||
- - Would most likely require Statbotics
|
||||
https://www.thebluealliance.com/avatars
|
||||
### Screenshots
|
||||
|Match scouting interface|Field editor|Teams data viewer|
|
||||
|-|-|-|
|
||||
||||
|
||||
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ android {
|
||||
dependenciesInfo {
|
||||
// Disables dependency metadata when building APKs.
|
||||
includeInApk = false
|
||||
// Disables dependency metadata when building Android App Bundles.
|
||||
// Disables dependency metadata when building Android App Bundles.5
|
||||
includeInBundle = false
|
||||
}
|
||||
|
||||
@@ -25,8 +25,8 @@ android {
|
||||
applicationId = "com.ridgebotics.ridgescout"
|
||||
minSdk = 24
|
||||
targetSdk = 34
|
||||
versionCode = 11 // **IMPORTANT** Increment this before releasing on github
|
||||
versionName = "1.4"// **IMPORTANT** Change this before releasing on github (<Year num since 2024>.<Update Version>)
|
||||
versionCode = 12 // **IMPORTANT** Increment this before releasing on github
|
||||
versionName = "2.0"// **IMPORTANT** Change this before releasing on github (<Year num since 2024>.<Update Version>)
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
@@ -157,28 +157,35 @@ public class ScoutingFragment extends Fragment {
|
||||
private void updateDashboard() {
|
||||
binding.textName.setText("Welcome, " + SettingsManager.getUsername() + "!");
|
||||
|
||||
int curMatchNum = SettingsManager.getMatchNum();
|
||||
int nextMatch;
|
||||
int teamNum = SettingsManager.getTeamNum();
|
||||
try {
|
||||
nextMatch = event.getNextTeamMatch(teamNum, curMatchNum).matchIndex;
|
||||
} catch (Exception e){
|
||||
AlertManager.error("Sorry, in event ("+evcode+"), your team number ("+teamNum+") wasn't found!", e);
|
||||
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)
|
||||
.body1()
|
||||
.build());
|
||||
if(event.matches.size() == 0) {
|
||||
binding.textMatchAlliance.setText("No Matches!");
|
||||
} else {
|
||||
int teamNum = SettingsManager.getTeamNum();
|
||||
int curMatchNum = SettingsManager.getMatchNum();
|
||||
|
||||
int informedBy = event.getMostInformedBy(teamNum, curMatchNum);
|
||||
binding.textMatchAlliance.setText("Match: " + (curMatchNum+1) + ", " + SettingsManager.getAllyPos());
|
||||
|
||||
int nextMatch;
|
||||
try {
|
||||
nextMatch = event.getNextTeamMatch(teamNum, curMatchNum).matchIndex;
|
||||
} catch (Exception e){
|
||||
AlertManager.error("Sorry, in event ("+evcode+"), your team number ("+teamNum+") wasn't found!", e);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
binding.infoBox.addView(new TextViewBuilder(getContext(), "Most informed by: Match " + informedBy)
|
||||
.body1()
|
||||
.build());
|
||||
binding.infoBox.addView(new TextViewBuilder(getContext(), "Our next match: Match " + nextMatch)
|
||||
.body1()
|
||||
.build());
|
||||
|
||||
int informedBy = event.getMostInformedBy(teamNum, curMatchNum);
|
||||
|
||||
|
||||
binding.infoBox.addView(new TextViewBuilder(getContext(), "Most informed by: Match " + informedBy)
|
||||
.body1()
|
||||
.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -165,12 +165,12 @@ public class TBASelectorFragment extends Fragment {
|
||||
try {
|
||||
Date startDate = format.parse(j.getString("start_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);
|
||||
}else if(currentTime.before(startDate)){
|
||||
row.setColor(tba_next);
|
||||
}else if(currentTime.after(startDate) && currentTime.before(endDate)){
|
||||
row.setColor(tba_current);
|
||||
}
|
||||
} catch (Exception 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){
|
||||
// Turns out the "Y" In YUV is the Luminance of the pixel.
|
||||
// Makes converting to greyscale 1000x easier
|
||||
@@ -94,12 +98,17 @@ public class CodeScannerView extends Fragment {
|
||||
final int width = image.getWidth();
|
||||
final int height = image.getHeight();
|
||||
|
||||
|
||||
|
||||
int[] pixels = new int[width * height];
|
||||
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;
|
||||
}
|
||||
|
||||
threshold(pixels, width, height);
|
||||
|
||||
Matrix matrix = new Matrix();
|
||||
|
||||
matrix.postRotate(90);
|
||||
@@ -112,6 +121,110 @@ public class CodeScannerView extends Fragment {
|
||||
|
||||
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) {
|
||||
|
||||
// CodeScanTask async = new CodeScanTask();
|
||||
|
||||
@@ -67,9 +67,9 @@ public class Colors {
|
||||
public static final int fileselector_unselected_color = 0x50006600;
|
||||
|
||||
// TBA
|
||||
public static final int tba_previous = 0x30FF0000;
|
||||
public static final int tba_current = 0x50ff0000;
|
||||
public static final int tba_next = 0x30FFFF00;
|
||||
public static final int tba_previous = Color.argb(30, 64,64,64);
|
||||
public static final int tba_current = 0x7f00ff00;
|
||||
public static final int tba_next = Color.argb(30, 192,192,192);
|
||||
public static final int tba_red = 0x50ff0000;
|
||||
public static final int tba_blue = 0x500000ff;
|
||||
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