11 Commits

Author SHA1 Message Date
Michael Mikovsky 99c3fa48dc Add block mean binarization 2025-12-06 11:45:23 -07:00
Daniel Carta e6073ded49 Update README.md 2025-11-04 12:03:29 -07:00
Daniel Carta 7503cefa09 Update Screenshots
Changed old screenshots to new ones
2025-11-04 12:01:58 -07:00
Michael Mikovsky 3b4737e6bc Change version 2025-10-08 20:37:30 -06:00
Michael Mikovsky 76d28e46cd Update version to 2.1 2025-10-08 20:27:05 -06:00
Michael Mikovsky 73308f2acc Fix TBA Event coloring 2025-10-08 20:23:11 -06:00
Michael Mikovsky c18a93cb1a Change dashboard if there are no matches 2025-10-08 20:06:25 -06:00
Michael Mikovsky 3ca601f080 Merge pull request #15 from Team4388/increasing-verbosity
Add more comments
2025-10-08 11:57:56 -06:00
Michael Mikovsky 4554db9dc7 Merge branch 'main' into increasing-verbosity 2025-10-08 11:57:48 -06:00
Michael Mikovsky b5b1100c2c Merge pull request #14 from Team4388/random-ui-fixes
Random UI fixes
2025-10-08 11:56:30 -06:00
Daniel Carta ef761003c8 Increased the verbosity 2025-07-24 10:59:22 -06:00
22 changed files with 168 additions and 51 deletions
Vendored
BIN
View File
Binary file not shown.
+4 -5
View File
@@ -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
+3 -3
View File
@@ -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"
} }
@@ -41,7 +41,6 @@ public class ScoutingArray {
continue; continue;
case CREATE: case CREATE:
new_values[i] = create_transfer((CreateTransferType) tv); new_values[i] = create_transfer((CreateTransferType) tv);
continue;
} }
} }
this.array = new_values; this.array = new_values;
@@ -72,12 +71,6 @@ public class ScoutingArray {
return get_data_type_by_UUID(tv.UUID); return get_data_type_by_UUID(tv.UUID);
} }
// private dataType rename_transfer(renameTransferType tv){
// dataType dt = get_data_type_by_name(tv.name);
// dt.name = tv.new_name;
// return dt;
// }
private RawDataType create_transfer(CreateTransferType tv){ private RawDataType create_transfer(CreateTransferType tv){
FieldType it = get_input_type_by_UUID(version+1, tv.UUID); FieldType it = get_input_type_by_UUID(version+1, tv.UUID);
switch (it.getValueType()){ switch (it.getValueType()){
@@ -32,12 +32,6 @@ public class ScoutingFile {
ByteBuilder bb = new ByteBuilder() ByteBuilder bb = new ByteBuilder()
.addString(filename); .addString(filename);
// byte[] data = Objects.requireNonNull(fileEditor.readFile(filename));
// for(int i = 0; i < data.length / 65535; i++){
// bb.addRaw(255, fileEditor.getByteBlock(data, i*65535, (i+1)*65535));
// }
bb.addRaw(255, Objects.requireNonNull(FileEditor.readFile(filename))); bb.addRaw(255, Objects.requireNonNull(FileEditor.readFile(filename)));
return bb.build(); return bb.build();
@@ -16,11 +16,14 @@ import java.util.stream.IntStream;
// Class to contain data for an entire event. // Class to contain data for an entire event.
// Easily encoded and decoded to binary format. // Easily encoded and decoded to binary format.
public class frcEvent { public class frcEvent {
public String eventCode;
// public static final int typecode = 254; Unused, no idea what this is
public String eventCode; //Current event code
public String name; public String name;
public ArrayList<frcMatch> matches; public ArrayList<frcMatch> matches;
public ArrayList<frcTeam> teams; public ArrayList<frcTeam> teams;
// Turns frcEvent into raw data
public byte[] encode() { public byte[] encode() {
try { try {
ByteBuilder bb = new ByteBuilder() ByteBuilder bb = new ByteBuilder()
@@ -46,6 +49,7 @@ public class frcEvent {
} }
} }
//Decodes the frcEvent
public static frcEvent decode(byte[] bytes) { public static frcEvent decode(byte[] bytes) {
try { try {
ArrayList<BuiltByteParser.parsedObject> objects = ArrayList<BuiltByteParser.parsedObject> objects =
@@ -74,6 +78,7 @@ public class frcEvent {
} }
} }
//Generates text
@NonNull @NonNull
public String toString() { public String toString() {
return ( return (
@@ -207,6 +207,7 @@ public class CheckboxType extends FieldType {
parent.addView(chart); parent.addView(chart);
} }
//TODO
public void addDataToTable(TableLayout parent, Map<Integer, List<RawDataType>> data){ public void addDataToTable(TableLayout parent, Map<Integer, List<RawDataType>> data){
} }
@@ -103,7 +103,7 @@ public class DropdownType extends FieldType {
// Dropdown view
public void add_individual_view(LinearLayout parent, RawDataType data){ public void add_individual_view(LinearLayout parent, RawDataType data){
if(data.isNull()) return; if(data.isNull()) return;
@@ -122,7 +122,7 @@ public class DropdownType extends FieldType {
// Generates N amount of colors, all opposite colors
private static int[] generateEquidistantColors(int N) { private static int[] generateEquidistantColors(int N) {
int[] colors = new int[N]; int[] colors = new int[N];
float[] hsv = new float[3]; // Hue, Saturation, Value float[] hsv = new float[3]; // Hue, Saturation, Value
@@ -138,6 +138,7 @@ public class DropdownType extends FieldType {
return colors; return colors;
} }
// Turns the dropdown into a pie chart in the compiled view
public void add_compiled_view(LinearLayout parent, RawDataType[] data){ public void add_compiled_view(LinearLayout parent, RawDataType[] data){
PieChart chart = new PieChart(parent.getContext()); PieChart chart = new PieChart(parent.getContext());
FrameLayout.LayoutParams layout = new FrameLayout.LayoutParams( FrameLayout.LayoutParams layout = new FrameLayout.LayoutParams(
@@ -171,7 +172,7 @@ public class DropdownType extends FieldType {
// Turns the dropdown into a line chart in the history view
public void add_history_view(LinearLayout parent, RawDataType[] data){ public void add_history_view(LinearLayout parent, RawDataType[] data){
LineChart chart = new LineChart(parent.getContext()); LineChart chart = new LineChart(parent.getContext());
FrameLayout.LayoutParams layout = new FrameLayout.LayoutParams( FrameLayout.LayoutParams layout = new FrameLayout.LayoutParams(
@@ -238,6 +239,7 @@ public class DropdownType extends FieldType {
parent.addView(chart); parent.addView(chart);
} }
//TODO
public void addDataToTable(TableLayout parent, Map<Integer, List<RawDataType>> data){ public void addDataToTable(TableLayout parent, Map<Integer, List<RawDataType>> data){
} }
@@ -221,6 +221,7 @@ public class FieldposType extends FieldType {
parent.addView(chart); parent.addView(chart);
} }
//TODO
public void addDataToTable(TableLayout parent, Map<Integer, List<RawDataType>> data){ public void addDataToTable(TableLayout parent, Map<Integer, List<RawDataType>> data){
} }
@@ -312,6 +312,7 @@ public class NumberType extends FieldType {
parent.addView(chart); parent.addView(chart);
} }
//TODO
public void addDataToTable(TableLayout parent, Map<Integer, List<RawDataType>> data){ public void addDataToTable(TableLayout parent, Map<Integer, List<RawDataType>> data){
} }
@@ -220,6 +220,7 @@ public class TextType extends FieldType {
} }
//TODO
public void addDataToTable(TableLayout parent, Map<Integer, List<RawDataType>> data){ public void addDataToTable(TableLayout parent, Map<Integer, List<RawDataType>> data){
} }
@@ -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()
@@ -181,4 +187,5 @@ public class ScoutingFragment extends Fragment {
.body1() .body1()
.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);
@@ -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;
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
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