Hardware

Overview

Because our test relies on fluorescence for a positive result, and as this fluorescence is invisible to the naked eye, a reliable and highly sensitive method of photodetection is required. BoviLock is a novel, low-cost, LED based fluorometer used to measure the fluorescence from the fluorophore-quencher separation caused by a positive result from our test. A prototype, open-source Lock-In Amplifier made from affordable and accessible electronic components, is used in conjunction with a mobile phone app to provide rapid feedback to users for our test. The inner frame to hold the optical and electronic components is 3D printed using a sustainable PLA, with an outer casing made from laser-cut wood to reduce ambient light interference with the test.

Hardware Theory and Design


Mycobacterium bovis is the causative agent of bovine tuberculosis (bTB), a chronic infectious disease that predominantly infects domestic cattle and various species of wildlife (e.g. deer and badgers). In the UK, cattle displaying bTB symptoms are systematically culled, with 21,298 culling occurring between April 2023 and March 2024 - a 5% increase from last year's 20,121 culls [1]. This costs the government £120 million a year, of which £50 million falls on farmers alone [1] .

But the impact of bTB extends beyond finances, having significant impacts on farmers and their family's mental wellbeing, not only from the culling of their herds but also from the mere threat of a positive bTB test result [1]. Additionally, while bTB is primarily expressed in cattle, humans in close contact with them are also susceptible to infection. In regions where subsistence farming is more prevalent, bTB transmission from livestock to their handlers results in approximately 140,000 human TB cases and 12,000 deaths per year [2][3]. It is therefore no exaggeration to say that, across the world, bTB represents a serious, global threat to livestock, rural economies and farmer's livelihoods and wellbeing [2][3].


Digital lock-in Amplification


Team Exeter is based in South-West England, an area that contains approximately a quarter of all English farms and where bTB is now endemic [1]. Consequently, this issue is very apparent in our local surrounding communities and something we wanted to put our efforts towards resolving. Due to the many areas bTB impacts, there are multiple avenues in which bTB could be addressed, a number of which are being enacted by the government. But our team decided to target the current testing regime.


Mobile Phone App


The Android Studio program: a list of files in use are in a pane on the left, a similar pane on the right shows a visualisation of a mobile phone, and in the centre is the code itself.

Our final device needed to be operable by a farmer or vet in the field. We decided that the best way of achieving this was by creating a mobile phone app, which could receive images from the device, display them, and give a reading of the likelihood of a bTB infection. Two main routes were explored: either a phone could take the pictures directly by inserting the camera end into the device, or it could receive images from a microcontroller in the device, which would include its own camera, via Bluetooth.
All programming took place in Flutter, via Android Studio. While it proved temperamental, it was fairly user-friendly, helpful to newcomers to the language (as we all were initially).

A picture.
Another picture.
A third picture.

Plan A, using the phone's inbuilt camera, was the simplest to achieve (and activated using the left button on the home screen). To make the app as versatile as possible, and adaptable to different phones, a list of available cameras is brought up for selection (currently for testing purposes 'camera 1' is automatically set, but this is simple to change). Once a photo is taken, it displays on the screen to allow a result to be recorded. This worked well, but requiring the system to work with any phone presented issues. Any calibration of the device will be camera-specific, so the user would need to perform this before they could use the test - and worse, the device box would have to be built so that any phone could both fit into it and have its camera appropriately placed. There must have been a better way... and there was, plan B. Building an app to work with Bluetooth complicated the jobs of both programmer and physical device builder, as the app would need to interface with many other devices, and our device would now need to contain its own controller, camera and Bluetooth communicator. App-wise we can currently scan for devices, but this brings up an error as the variable holding the device name must be initialised (but there are no devices until the scan has taken place...). A simple communication page to allow the user to send signals to the device has been developed, and is presented within the code below. The physical hardware also encountered issues - the camera was found to be ineffective for fluorescent concentrations below 100 nM. A future extension to this project could work in improving this.

Full code
main.dart

import 'package:flutter/material.dart';
import 'package:cowtest/home.dart';
import 'package:cowtest/cowtest.dart';
import 'package:cowtest/Camera.dart';
import 'package:cowtest/loading.dart';
import 'package:camera/camera.dart';
import 'package:cowtest/btinitial.dart';

Future main() async {

    // Ensure that plugin services are initialized so that `availableCameras()`
    // can be called before `runApp()`
    WidgetsFlutterBinding.ensureInitialized();

    // Obtain a list of the available cameras on the device.
    final cameras = await availableCameras();

    // Get a specific camera from the list of available cameras.
    final firstCamera = cameras.first;

    runApp(MaterialApp(
      initialRoute: '/home',
      routes: {
      '/': (context) => const Loading(),
      '/home': (context) => const Home(),
      '/cowtest': (context) => const CowTest(),
      '/Camera': (context) => TakePictureScreen(camera: firstCamera),
      //'/bluetooth': (context) => const Bluetooth(),
      '/btinitial': (context) => const BTInitial(),
      //'/btconnect': (context) => const BTConnect(),
      },
    ));
}

loading.dart

import 'package:flutter/material.dart';
class Loading extends StatefulWidget {

    const Loading({super.key});
    @override
    _LoadingState createState() => _LoadingState();
}
class _LoadingState extends State {
    @override
    Widget build(BuildContext context) {
      return const Scaffold(
        body: SafeArea(child: Text('cow test'))
      );
    }
}

home.dart

import 'package:flutter/material.dart'; class Home extends StatefulWidget {

    const Home({super.key});
    @override
    _HomeState createState() => _HomeState();
} class _HomeState extends State {
    @override
    Widget build(BuildContext context) {
      return Scaffold(
        backgroundColor: Colors.green[200],
        appBar: AppBar(
          automaticallyImplyLeading: false,
          backgroundColor: Colors.green[500],
          title: const Text('Cow App (alpha v0.3)'),
        ),
        body: Column(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: [
              Container(
                color:Colors.green[300],
                child: const Center(
                  child: Text(
                    "'Jersey update' (Bluetooth connection)",
                  ),
                ),
              ),
              Container(
                child:Image.asset('resources/logo.png'),
              ),
              Container(
                color:Colors.green[300],
                child: const Center(
                  //child: Text(
                  // "Next on to do list: get hold of an Arduino and begin physical testing."
                  //),
                ),
              ),
              Container(
                child:Image.asset('resources/cow.gif'),
              ),
              Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Container(
                    padding:const EdgeInsets.all(25.0),
                    child: FloatingActionButton(
                      onPressed: () {
                        Navigator.pushNamed(context, '/Camera');
                      },
                      child: const Icon(Icons.camera_alt)
                  )
                  ),
                  Container(
                    padding:const EdgeInsets.all(25.0),
                    child: FloatingActionButton(
                      onPressed: () {
                        Navigator.pushNamed(context, '/btinitial');
                        },
                        backgroundColor: Colors.brown[500],
                        child: const Icon(Icons.bluetooth,
                        color: Colors.green,
                        ),
                      ),
                    ),
                    ],
                ),
              ]
          )
        );
      }
    }

Camera.dart

import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:io';
// A screen that allows users to take a picture using a given camera.
class TakePictureScreen extends StatefulWidget {

    const TakePictureScreen({
      super.key,
      required this.camera,
    });
    final CameraDescription camera;
    @override
    TakePictureScreenState createState() => TakePictureScreenState();
}
class TakePictureScreenState extends State {
    late CameraController _controller;
    late Future _initializeControllerFuture;
    @override
    void initState() {
      super.initState();
      // To display the current output from the Camera,
      // create a CameraController.
      _controller = CameraController(
        // Get a specific camera from the list of available cameras.
        widget.camera,
        // Define the resolution to use.
        ResolutionPreset.medium,
      );
    // Next, initialize the controller. This returns a Future.
    _initializeControllerFuture = _controller.initialize();
}
@override
void dispose() {
    // Dispose of the controller when the widget is disposed.
    _controller.dispose();
    super.dispose();
}
@override
Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Cow Camera'),
      backgroundColor: Colors.green[1000],),
      // You must wait until the controller is initialized before displaying the
      // camera preview. Use a FutureBuilder to display a loading spinner until the
      // controller has finished initializing.
      body: FutureBuilder(
        future: _initializeControllerFuture,
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.done) {
            // If the Future is complete, display the preview.
            return CameraPreview(_controller);
          } else {
            // Otherwise, display a loading indicator.
            return const Center(child: CircularProgressIndicator());
          }
        },
      ),
      floatingActionButton: FloatingActionButton(
    // Provide an onPressed callback.
    onPressed: () async {
      // Take the Picture in a try / catch block. If anything goes wrong,
      // catch the error.
      try {
        // Ensure that the camera is initialized.
        await _initializeControllerFuture;
        // Attempt to take a picture and get the file `image`
        // where it was saved.
        final image = await _controller.takePicture();
        if (!context.mounted) return;
        // If the picture was taken, display it on a new screen.
        await Navigator.of(context).push(
          MaterialPageRoute(
            builder: (context) => DisplayPictureScreen(
              // Pass the automatically generated path to
              // the DisplayPictureScreen widget.
              imagePath: image.path,
            ),
          ),
        );
      } catch (e) {
        // If an error occurs, log the error to the console.
        print(e);
      }
    },
    child: const Icon(Icons.camera_alt),
), ); } }
// A widget that displays the picture taken by the user.
class DisplayPictureScreen extends StatelessWidget {
    final String imagePath;
    const DisplayPictureScreen({super.key, required this.imagePath});
    @override
    Widget build(BuildContext context) {
      return Scaffold(
        appBar: AppBar(title: const Text('Image')),
        // The image is stored as a file on the device. Use the `Image.file`
        // constructor with the given path to display the image.
        body: Image.file(File(imagePath)),
      );
    }
}

cowtest.dart

import 'package:flutter/material.dart';
class CowTest extends StatefulWidget {

    const CowTest({super.key});
    @override
    _CowTestState createState() => _CowTestState();
}
class _CowTestState extends State {
    @override
    Widget build(BuildContext context) {
      return Scaffold(
        appBar: AppBar(
          backgroundColor: Colors.green[500],
          title: const Text('Cow App (alpha v0.3)'),
        ),
        body: const SafeArea(child: Text('cow test'))
      );
    }
}

bluetooth.dart

import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart';
class ChatPage extends StatefulWidget {

    final BluetoothDevice server;
    const ChatPage({super.key, required this.server});
    @override
    _ChatPage createState() => _ChatPage();
      }
      class _Message {
        int whom;
        String text;
        _Message(this.whom, this.text);
      }
      class _ChatPage extends State {
        static const clientID = 0;
        late BluetoothConnection connection;
        List<_Message> messages = <_Message>[];
        String _messageBuffer = '';
        final TextEditingController textEditingController =
        TextEditingController();
        final ScrollController listScrollController = ScrollController();
        bool isConnecting = true;
        bool get isConnected => connection.isConnected;
        bool isDisconnecting = false;
        @override
        void initState() {
          super.initState();
          BluetoothConnection.toAddress(widget.server.address).then((connection) {
            print('Connected to the device');
            connection = connection;
            setState(() {
              isConnecting = false;
              isDisconnecting = false;
            });
            connection.input?.listen(_onDataReceived).onDone(() {
              // Example: Detect which side closed the connection
              // There should be `isDisconnecting` flag to show are we are (locally)
              // in middle of disconnecting process, should be set before calling
              // `dispose`, `finish` or `close`, which all causes to disconnect.
              // If we except the disconnection, `onDone` should be fired as result.
              // If we didn't except this (no flag set), it means closing by remote.
              if (isDisconnecting) {
                print('Disconnecting locally!');
              } else {
                print('Disconnected remotely!');
              }
              if (mounted) {
                setState(() {});
              }
            });
          }).catchError((error) {
            print('Cannot connect, exception occured');
            print(error);
          });
        }
        @override
        void dispose() {
          // Avoid memory leak (`setState` after dispose) and disconnect
          if (isConnected) {
            isDisconnecting = true;
            connection.dispose();
            //connection = null; (I commented this out as it was breaking the code, not sure if it will work now...
          }
          super.dispose();
        }
        @override
        Widget build(BuildContext context) {
          final List list = messages.map((message) {
            return Row(
              mainAxisAlignment: message.whom == clientID
                ? MainAxisAlignment.end
                : MainAxisAlignment.start,
              children: [
                Container(
                  padding: const EdgeInsets.all(12.0),
                  margin: const EdgeInsets.only(bottom: 8.0, left: 8.0, right: 8.0),
                  width: 222.0,
                  decoration: BoxDecoration(
                    color:
                    message.whom == clientID ? Colors.blueAccent : Colors.grey,
                    borderRadius: BorderRadius.circular(7.0)),
                  child: Text(
                    (text) {
                    return text == '/shrug' ? '¯\\_(ツ)_/¯' : text;
                    }(message.text.trim()),
                    style: const TextStyle(color: Colors.white)),
                ),
              ],
            );
          }).toList();
          return Scaffold(
            appBar: AppBar(
              title: (isConnecting
                ? const Text('Connecting chat...')
                : isConnected
                ? const Text('Live chat')
                : const Text('Chat log'))),
            body: SafeArea(
              child: Column(
                children: [
                  Container(
                    padding: const EdgeInsets.all(5),
                    width: double.infinity,
                    child: FittedBox(
                      child: Row(
                        children: [
                          FloatingActionButton(
                            onPressed: isConnected ? () => _sendMessage('1') : null, child: ClipOval(child: Image.asset('images/ledOn.png')),
                          ),
                          FloatingActionButton(
                            onPressed: isConnected ? () => _sendMessage('0') : null,
                            child: ClipOval(child: Image.asset('images/ledOff.png')),
                          ),
                        ],
                      ),
                    ),
                  ),
                  Flexible(
                    child: ListView(
                    padding: const EdgeInsets.all(12.0),
                    controller: listScrollController,
                    children: list),
                  ),
                  Row(
                    children: [
                      Flexible(
                        child: Container(
                          margin: const EdgeInsets.only(left: 16.0),
                          child: TextField(
                            style: const TextStyle(fontSize: 15.0),
                            controller: textEditingController,
                            decoration: InputDecoration.collapsed(
                              hintText: isConnecting
                                ? 'Wait until connected...'
                                : isConnected
                                ? 'Type your message...'
                                : 'Chat got disconnected',
                              hintStyle: const TextStyle(color: Colors.grey),
                            ),
                            enabled: isConnected,
                          ),
                        ),
                      ),
                      Container(
                        margin: const EdgeInsets.all(8.0),
                        child: IconButton(
                          icon: const Icon(Icons.send),
                          onPressed: isConnected
                            ? () => _sendMessage(textEditingController.text) : null),
                      ),
                    ],
                  )
                ],
              ),
            ),
          );
        }
        void _onDataReceived(Uint8List data) {
          // Allocate buffer for parsed data
          int backspacesCounter = 0;
          for (var byte in data) {
            if (byte == 8 || byte == 127) {
              backspacesCounter++;
            }
          }
          Uint8List buffer = Uint8List(data.length - backspacesCounter);
          int bufferIndex = buffer.length;
          // Apply backspace control character
          backspacesCounter = 0;
          for (int i = data.length - 1; i >= 0; i--) {
            if (data[i] == 8 || data[i] == 127) {
              backspacesCounter++;
            } else {
              if (backspacesCounter > 0) {
                backspacesCounter--;
              } else {
                buffer[--bufferIndex] = data[i];
              }
            }
          }
          // Create message if there is new line character
          String dataString = String.fromCharCodes(buffer);
          int index = buffer.indexOf(13);
          if (~index != 0) {
            setState(() {
              messages.add(
                _Message(
                  1,
                  backspacesCounter > 0
                    ? _messageBuffer.substring(
                    0, _messageBuffer.length - backspacesCounter)
                    : _messageBuffer + dataString.substring(0, index),
                ),
              );
              _messageBuffer = dataString.substring(index);
            });
          } else {
            _messageBuffer = (backspacesCounter > 0
              ? _messageBuffer.substring(
              0, _messageBuffer.length - backspacesCounter)
              : _messageBuffer + dataString);
          }
        }
        void _sendMessage(String text) async {
          text = text.trim();
          textEditingController.clear();
          if (text.isNotEmpty) {
            try {
              connection.output.add(utf8.encode("$text\r\n"));
              await connection.output.allSent;
              setState(() {
                messages.add(_Message(clientID, text));
              });
              Future.delayed(const Duration(milliseconds: 333)).then((_) {
                listScrollController.animateTo(
                  listScrollController.position.maxScrollExtent,
                  duration: const Duration(milliseconds: 333),
                  curve: Curves.easeOut);
              });
            } catch (e) {
            // Ignore error, but notify state
            setState(() {}); }
          }
        }
      }

btconnect.dart

import 'package:flutter/material.dart'; import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart';
import 'dart:async';
import 'package:cowtest/BTDevice.dart';
class SelectBondedDevicePage extends StatefulWidget {

    /// If true, on page start there is performed discovery upon the bonded devices.
    /// Then, if they are not avaliable, they would be disabled from the selection.
    final bool checkAvailability;
    final Function onCahtPage;
    const SelectBondedDevicePage(
      {super.key, this.checkAvailability = true, required this.onCahtPage});
    @override
    _SelectBondedDevicePage createState() => _SelectBondedDevicePage();
    //TODO: this might also break stuff (line above)
}
enum _DeviceAvailability {
    no,
    maybe,
    yes,
}
class _DeviceWithAvailability extends BluetoothDevice {
    BluetoothDevice device;
    _DeviceAvailability availability;
    int rssi=0;
    _DeviceWithAvailability(
      this.device, this.availability) : super(address: '')
    ;
}
class _SelectBondedDevicePage extends State {
    List<_DeviceWithAvailability> devices = <_DeviceWithAvailability>[];
    // Availability
    StreamSubscription _discoveryStreamSubscription=0 as StreamSubscription;
    // TODO: fix this line, it breaks the whole thing!
    bool _isDiscovering=false;
    _SelectBondedDevicePage();
    @override
    void initState() {
      super.initState();
      _isDiscovering = widget.checkAvailability;
      if (_isDiscovering) {
        _startDiscovery();
      }
      // Setup a list of the bonded devices
      FlutterBluetoothSerial.instance
          .getBondedDevices() .then((List bondedDevices) {
        setState(() {
          devices = bondedDevices
            .map(
              (device) => _DeviceWithAvailability(
            device,
            widget.checkAvailability
              ? _DeviceAvailability.maybe
              : _DeviceAvailability.yes
            ),
          )
          .toList();
        });
      });
    }
    void _restartDiscovery() {
      setState(() {
        _isDiscovering = true;
      });
      _startDiscovery();
    }
    void _startDiscovery() {
      _discoveryStreamSubscription =
        FlutterBluetoothSerial.instance.startDiscovery().listen((r) {
          setState(() {
            Iterator i = devices.iterator;
            while (i.moveNext()) {
              var device = i.current;
              if (device.device == r.device) {
                device.availability = _DeviceAvailability.yes;
                device.rssi = r.rssi;
              }
            }
          });
        });
      _discoveryStreamSubscription.onDone(() {
        setState(() {
          _isDiscovering = false;
        });
      });
    }
    @override
    void dispose() {
      // Avoid memory leak (`setState` after dispose) and cancel discovery
      _discoveryStreamSubscription.cancel();
      super.dispose();
    }
    @override
    Widget build(BuildContext context) {
      List list = devices
        .map(
          (device) => BluetoothDeviceListEntry(
        device: device.device,
        // rssi: _device.rssi,
        // enabled: _device.availability == _DeviceAvailability.yes,
        onTap: () {
          widget.onCahtPage(device.device);
        },
      ), )
        .toList();
      return ListView(
        children: list,
      );
      // return Scaffold(
      // appBar: AppBar(
      // title: Text('Select device'),
      // actions: [
      // _isDiscovering
      // ? FittedBox(
      // child: Container(
      // margin: new EdgeInsets.all(16.0),
      // child: CircularProgressIndicator(
      // valueColor: AlwaysStoppedAnimation(
      // Colors.white,
      // ),
      // ),
      // ),
      // )
      // : IconButton(
      // icon: Icon(Icons.replay),
      // onPressed: _restartDiscovery,
      // )
      // ],
      // ),
      // body: ListView(children: list),
      // );

    }
}
// class BTConnect extends StatefulWidget {
// const BTConnect({super.key});
//
// @override
// _BTConnectState createState() => _BTConnectState();
// }
//
// class _BTConnectState extends State {
// @override
// Widget build(BuildContext context) {
// return Scaffold(
// appBar: AppBar(
// backgroundColor: Colors.green[500],
// title: const Text('Cow App (alpha v0.3)'),
// ),
// body: const SafeArea(child: Text('cow test'))
// );
// }
// }

BTDevice.dart

import 'package:flutter/material.dart';
import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart';
class BluetoothDeviceListEntry extends StatelessWidget {

    final Function onTap;
    final BluetoothDevice device;
    const BluetoothDeviceListEntry({super.key, required this.onTap, required this.device});
    @override
    Widget build(BuildContext context) {
      return ListTile(
        onTap: onTap(),
        leading: const Icon(Icons.devices),
        title: Text(device.name ?? "Unknown device"),
        subtitle: Text(device.address.toString()),
        trailing: FloatingActionButton(
          onPressed: onTap(),
          backgroundColor: Colors.blue,
          child: const Text('Connect'),
        ),
      );
    }
}

btinitial.dart

import 'package:flutter/material.dart';
import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart';
import 'package:cowtest/btconnect.dart';
import 'package:cowtest/bluetooth.dart';
class BTInitial extends StatefulWidget {

    const BTInitial({super.key});
    @override
    _BTInitialState createState() => _BTInitialState();
}
class _BTInitialState extends State {
    @override
    Widget build(BuildContext context) {
      FutureBuilder(
        future: FlutterBluetoothSerial.instance.requestEnable(),
        builder: (context, future) {
          if (future.connectionState == ConnectionState.waiting) {
            return const Scaffold(
              body: SizedBox(
              height: double.infinity,
              child: Center(
                child: Icon(
                Icons.bluetooth_disabled,
                size: 200.0,
                color: Colors.blue))));
          } else if (future.connectionState == ConnectionState.done) {
            return const BTHome();
          } else {
            return const BTHome(); // this might break the code, remove if it does!
          }
        },
      );
      return const BTHome();
    }
}
class BTHome extends StatelessWidget {
    const BTHome({super.key});
    @override
    Widget build(BuildContext context) {
      return SafeArea(
        child: Scaffold(
          appBar: AppBar(
            title: const Text("Connection"),
          ),
          body: SelectBondedDevicePage(
            onCahtPage: (device1) {
              BluetoothDevice device = device1;
              Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) {
                    return ChatPage (server: device);
                  },
                ),
              );
            },
          ),
        ),
      );
    }
}
// return Scaffold(
// appBar: AppBar(
// backgroundColor: Colors.green[500],
// title: const Text('Cow App (alpha v0.3)'),
// ),
// body: const SafeArea(child: Text('cow test'))
// );
// }
//}


Proof of Concept Qualitive Experiment


Our project aims to address as many of the aforementioned issues related to the current skin test by using synthetic biology to design, construct and validate a practical, sensitive and rapid diagnostic test for bTB that farmers can use to monitor the health of their livestock. In this case we hope to reduce the direct and indirect impacts of Bovine Tuberculosis on cattle farmers and in turn protect their wellbeing and livelihoods. Our proposed test will be informed by the experience of end-users to be simple, self-contained, inexpensive and shelf-stable.

  • Figures

  • Currently, bTB testing in this region is compulsory and relies on costly veterinary intervention, often requiring weeks of additional preperation on the farmer's part. This can can be stressful for both the farmer and the cattle involved. [1][4]. Cattle must be put in a crush twice within a 2 or 3 days period; first to administer the test injection, then again to analyse the results. This entire process can be stressful to both the herd and farmer, putting both parties at risk of harm [7].

    Furthermore, cattle can be deemed as an Inconclusive Reactor (IR) after the second test, meaning they must be isolated and retested 60 days later, leading to further anxiety for the animal[5].

    text: current skin tests are often stressful for farmers and cattle, putting them at risk of harm. Gif of cattle walking into crush with farmer and cattle look stressed.

    Validation


    Our project aims to address as many of the aforementioned issues related to the current skin test by using synthetic biology to design, construct and validate a practical, sensitive and rapid diagnostic test for bTB that farmers can use to monitor the health of their livestock. In this case we hope to reduce the direct and indirect impacts of Bovine Tuberculosis on cattle farmers and in turn protect their wellbeing and livelihoods. Our proposed test will be informed by the experience of end-users to be simple, self-contained, inexpensive and shelf-stable.


    Initial Quantitative Experiments


    Our project aims to address as many of the aforementioned issues related to the current skin test by using synthetic biology to design, construct and validate a practical, sensitive and rapid diagnostic test for bTB that farmers can use to monitor the health of their livestock. In this case we hope to reduce the direct and indirect impacts of Bovine Tuberculosis on cattle farmers and in turn protect their wellbeing and livelihoods. Our proposed test will be informed by the experience of end-users to be simple, self-contained, inexpensive and shelf-stable.


    Further Developments


    Our project aims to address as many of the aforementioned issues related to the current skin test by using synthetic biology to design, construct and validate a practical, sensitive and rapid diagnostic test for bTB that farmers can use to monitor the health of their livestock. In this case we hope to reduce the direct and indirect impacts of Bovine Tuberculosis on cattle farmers and in turn protect their wellbeing and livelihoods. Our proposed test will be informed by the experience of end-users to be simple, self-contained, inexpensive and shelf-stable.

    Click for References
    back to top button