Sie sind auf Seite 1von 36

Design

of a Mobile Platform for Acquistion, Synthesis, and Analysis of Programs for Exercise Instruction

by James Cash Supervisor: Jason Foster April 2012

Abstract My thesis is a design thesis, concerning the design and implementation of a system to facilitate the acquisition, organization, and analysis of instructor-lead exercise programs, speci cally the program produced by Les Mills. To this end, I designed and implemented an iOS application (speci cally targeted to the iPhone) as well as a server backend which the application acquires data from. e program can be used by the instructor to create their own custom workouts based on combinations of the existing exercises provided by Les Mills, while being able to analyze their workouts to ensure that the workouts are still balanced and eective.

Acknowledgements

Much thanks to my advisor, Jason Foster for all his help, both as an advisor and as a user of this system. anks to e Noun Project1 for their excellent library of icons. anks to Joe Conway and Aaron Hillegass for their excellent Big Nerd Ranch Guide: iOS Programming book, a vital resource for learning the iOS platform.

http://thenounproject.com

Contents

1 Introduction 2 Background 3 Methods 4 Results and Discussion 5 Conclusion A Server API B Workout Analysis C Workout Model

7 9 13 15 19 21 25 31

List of Figures
2.1 4.1 4.2 4.3 Data model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 Serverside Track Select . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 Serverside Analytics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 Client Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17

Chapter 1 Introduction
e purpose of this design thesis was to create a system which would streamline the process by which instructors of the Les Mills1 tness classes put together their workouts. As it currently stands, the company releases a physical booklet once a month containing the information for a given release, consisting of a series of tracks, with a variety of exercises in each track. It is very common for the instructors to want to mix-and-match tracks from various dierent releases into their own custom workout on a weekly basis. However, doing this manually is an extremely time-consuming process that is prohibitive to most of the instructors. Additionally, when creating a custom workout on ones own, it is very easy to inadvertently create an unbalanced workout, which may lead to the participants having some body part too heavily worked, another part not worked enough, or simply bored by having too-similar exercises right after each other. To solve this problem, I designed and implemented a mobile application (with a server backend) which will allow users to easily select tracks from a list of available releases, view the detailed information for each track, stitch them together into a custom workout, and nally analyze the workout for potential problems.

http://www.lesmills.com

Chapter 2 Background
Given the design nature of the thesis, the literature review primarily consisted of researching the various platforms on which to base the implementation of the system. e implementation of the server side component of the system was fairly unconstrained, as it would only be communicating with the clientside component via standard HTTP methods. e primary metric for the implementation was therefore developer familiarity and preference and secondarily ease of deployment. I therefore chose to use the Python programming language, as it is a system that I am very familiar with and have a great deal of real-world experience with. Additionally my deployment venue of choice, Heroku1 , recently added support for Python, meaning I could easily deploy and update the system using a familiar toolchain and work ow. Furthermore, since the components primary role is to process data and deliver it to the client component via a simple HTTP API, a web framework was desired which would allow this behaviour to be implemented easily without needing a great deal of extraneous other functionality. e most popular Python web frameworks, Django2 and Pyramid/Pylons3 , were therefore rejected, as they are both intended for much more fully-featured web applications with very rich client user interfaces. While the Flask4 framework was attractive, ultimately Web.py5 was chosen, as it seemed to be the simplest framework available which provided all the requisite functionality without requiring a great deal of boilerplate and allowing very exible structuring of
1 2 3 4 5

Heroku (http://heroku.com) is a popular cloud-based platform for deploying webapps.


https://www.djangoproject.com http://www.pylonsproject.org http://flask.pocoo.org http://webpy.org

the code. On a more granular level, there were several important design decisions which had to be made for the serverside implementation, the most crucial of which was the data model used to represent the exercise tracks and the associated workout information. While the data initially appeared to be structured enough to allow the information to represented completely in a static and rigorous manner, examination of other systems designed to track workout information in conjunction with deeper analysis of the Les Mills-provided releases showed the diculties of this approach: e many variations on both individual exercises and combinations thereof are too numerous and free-form to allow for the de nition of a static speci cation which can completely describe all possible workouts. erefore, the data model shown in Figure 2.1 was eventually decided upon. For more details on how this structure allows for a good compromise between structure and exibility, see chapter 3.

Figure 2.1: Data model For the client component, iOS was chosen as the platform on which to develop based on several things. Firstly, the popularity of the iPhone and iPod Touch: Informal surveys of the instructors indicated that the vast majority of them owned at least one iOS device, while other mobile platforms such as Android or Windows Phone enjoy much less traction among the target demographic. Secondly, the development environment provided by Apple for writing iOS applications is one with which I had some existing familiarity, thereby allowing more time to be spent on implementing the design rather 10

than on learning to use the toolkit. Lastly, unlike an HTML5 webapp, using a native client allows for a seamless oine experience, which was an important design consideration. Within the constraints of the iOS platform, I investigated various third-party frameworks and toolkits, such as the OmniGroups various frameworks6 , but ultimately decided to try to use only the standard iOS libraries, since the relatively straight-forward nature of the application did not seem to justify the inclusion of another set of dependencies nor the learning curve required to determine the optimal use of the libraries over the built-in facilities. erefore, I elected to use the built-in iOS features for the various user interface, HTTP API access, data storage and retrieval features.

e OmniGroup, the company behind several very popular OS X and iOS applications, has made a number of developer tools and libraries available online at http://www.omnigroup.com/company/developer/

11

12

Chapter 3 Methods
e rst phase of the design was to determine the architecture of the serverside component. As mentioned above, the data model required a blend of well-de ned and free-form components, with the model shown in Figure Figure 2.1 ultimately decided upon. is data structure was the result of a great deal of evolution and experimentation and changed several times over the course of implementing the system as new wrinkles of the way in which the workouts where represented made themselves apparent. As is clear in the diagram, there is a clear hierarchical structure to the data: A release has many blocks, each block has many exercises, each exercise has many moves. Within these entities, however, there are several elds which are essentially just free-form text, the most important of which are the
description

elds on the moves and blocks. Since the description for each move and block is untyped

and non-normalized, it allows for a great deal of exibility in describing the overall workout. One diculty that this data model gave rise to was that the analytics component became dicult, since there was not a great deal of information about exactly what the eect of each move was on the performers. However, while there is no formal structure of the move descriptions, there is enough consistency in the form of the description that I was able to come up with some heuristic approaches that had excellent results. Speci cally, by constructing a chain of regular expressions, I was able to determine the nub of each move (i.e. determine if the given move is fundamentally a run, a jump, a punch, etc) nearly perfectly. For more detail as to how the analysis was performed, see Appendix B. One of the issues that arose in the implementation of the client component was the data storage and retrieval system. As discussed in chapter 2, I decided to use the built-in iOS features for the system. 13

As part of this decision, I elected to use Apples CoreData framework to represent and persist both the tracks and workouts. While this system worked well for storing the tracks, since it has a natural concept of hierarchical structure that meshes well with the data structure used to represent the tracks on the server, problems arose when implementing the workout data model. e design of the workout implied that each workout contains references to all of the tracks of which it consists: However, this relationship was very dicult to represent appropriately using the CoreData model. After spending a great deal of time looking into this problem, the workaround eventually discovered was to simply store a key in the workout for each track. is solution, while inelegant, worked well and was able to be abstracted over in such a way that the rest of the application was insulated from this issue. For more detail as to how this was done, see Appendix C.

14

Chapter 4 Results and Discussion


e rst part of the system to be created was the serverside component. While its ultimate purpose was to simply be a data store which the client would access, it also provided the opportunity to prototype the analytics system. erefore, while not part of the end product, the user interface the server presented (shown in Figure 4.1 and Figure 4.2) was still very useful in terms of experimenting with the data model and provided very useful information for the implementation of the client. It was in the process of implementing this component that the data model was nalized and where the initial concepts for the types of analysis (and rough ideas concerning the implementation) were concieved.

Figure 4.1: Serverside Track Select

15

Figure 4.2: Serverside Analytics e implementation of the client platform was ultimately successful, in that it performed the requisite tasks of being able to acquire tracks ( 4.3b, 4.3c), organize them into workouts ( 4.3d, 4.3e), and analyze the workouts ( 4.3f). is process of designing a custom workout alone represents a time savings on the order of an hour for the users over the process of manually splicing together the individual tracks from various releases. e API which the server exposes to the client is fairly straightforward (see Appendix A for details). It provides methods to: Get the entire catalog Get a preview for a given track

get/catalog get/preview get/full

Get the full information for a given track Provide analysis for a given workout (not used by the client)

analyze/analyze

analyze/recommend Recommend addtional tracks for a given workout (not used by the client)

e client primarily uses the get/catalog and get/full methods. e get/catalog method is used to populate the list of available tracks the client displays when showing the available tracks to download. 16

(a) Main Interface

(b) Track Catalog

(c) Track Detail

(d) Workout List

(e) Workout Detail

(f ) Workout Analysis

Figure 4.3: Client Interface e get/full method is then used to download the full information for tracks which the user has opted to download and can then be used to create custom workouts. e interface of the client is presently very basic, as all of the development eort was focused on implementing functionality, with no spare cycles for creating artwork. All the icon artwork was therefore acquired from Creative Commons licenced sources, speci cally e Noun Project1 . at being said, while the interface is very simple, it is nonetheless complete and usable additional interface work would probably be necessary to make the client a fully- edged product, it is not necessary for the current level of functionality. As discussed in the previous section, while there were some internal issues which arose, all of them were able to be contained well enough that none of these problems were apparent to the end-user of
1

http://thenounproject.com

17

the application. is is an excellent example of the advantages of well-thought out software design: By containing and abstracting over de ciencies in the system, a consistent and complete product is able to be constructed out of an inconsistent and incomplete substrate.

18

Chapter 5 Conclusion
Overall, this design project was successful: A real problem was identi ed, a solution was carefully designed and successfully implemented. ere are, however, many opportunities for improvement remaining. In no particular order, the following features were considered but not yet added: Integrate the Les Mills-provided videos with the track information. More analytics the architecture of the analytics structure was speci cally designed to allow easy addition of more analysis engines. Exporting of the completed workouts to complete the cycle, it would be good if the users could generate a PDF and video for their custom workout. General enhancement of the user interface. e video integration was not present simply because of a lack of data splitting up the Les Mills videos into component tracks is a very time-consuming process to automate and was deemed to not be the best use of time. Similarly, the other features were just victims of time constraints and could easily be implemented if it was desired to publish the application as a fully- edged professional product. All of the code for both the server and client component are available online at https://github.com/
jamesnvc/Thesis-serverside

and https://github.com/jamesnvc/MusashiClient respectively.

19

20

Appendix A Server API


e HTTP API which the server exposes (using web.py) looks as follows:
import web

urls = ( /, index, /analyze, analyze, /api/(.*), api, /(favicon.ico), static, /s/(.*), static, /authorize, auth )

e essential part here are that requests can be sent to /api/ which will be handled by the api class. is dispatch part of the class looks as follows:
class Api(object): methods = { get: { catalog: get_catalog,

21

preview: get_preview, full: get_full }, analyze: { analyze: analyze_workout, recommend: recommend_tracks }, }

is means, for example, a request to

/api/get/catalog

will call the

Api.get_catalog

method to

send the client the full catalog. ose methods are reproduced below:
def get_catalog(self, _): Handle a request to get a list of the available tracks.

Returns: A JSON list of tracks available. return self.to_json(self.db.select(tracks))

def get_preview(self, args): Handle a request to get previews of a given track or tracks.

Arguments: - args: web input with a tracks field which is a JSON list of track ids.

Returns: A json list of track preview data. return self.to_json(self.db.select(

22

tracks, where=id in $ids, vars={ids: json.loads(args.tracks)}))

def get_full(self, args): Handle a request to get full versions of a given track or tracks.

Arguments: - args: web input with a tracks field which is a JSON list of track ids.

Returns: A json list of full track data. track_ids = json.loads(args.tracks) tracks = list(self.db.select(tracks, where=id in $ids, vars=dict(ids=track_ids))) for track in tracks: track.blocks = list(self.db.select(blocks, where=track_id = $track_id, order=sequence, vars=dict(track_id=track.id))) for block in track.blocks: block.exercises = list(self.db.select( exercises, where=block_id = $block_id, order=start_time, vars=dict(block_id=block.id))) for exercise in block.exercises: exercise.moves = list(self.db.select( moves, where=exercise_id = $exercise_id, order=sequence, vars=dict(exercise_id=exercise.id)))

23

return self.to_json(tracks)

24

Appendix B Workout Analysis


In WorkoutAnalyzer.h
@interface WorkoutAnalyzer : NSObject + (WorkoutAnalyzer *)defaultAnalyzer;

- (NSDictionary *)analyze:(Workout *)workout; - (NSString *)analyzeForOveruse:(Workout *)workout; @end

In WorkoutAnalyzer.m
- (NSString *)analyzeForOveruse:(Workout *)workout { NSArray *tracks = [workout fullTracks]; NSComparisonResult (^cmpBlk) (id obj1, id obj2) = ^(id obj1, id obj2) { if ([obj1 integerValue] > [obj2 integerValue]) { return (NSComparisonResult)NSOrderedDescending; }

if ([obj1 integerValue] < [obj2 integerValue]) {

25

return (NSComparisonResult)NSOrderedAscending; } return (NSComparisonResult)NSOrderedSame; }; NSMutableArray *freqs = [[NSMutableArray alloc] init]; for (FullTrack *track in tracks) { [freqs addObject:[track moveFrequencies]]; } /* Compare adjacent frequency dictionaries to see if there is overlap in the three most frequent moves */ NSString *fmtStr = @Tracks %d and %d may be too similiar!; NSMutableArray *warnings = [[NSMutableArray alloc] init]; for (int i = 1; i < [freqs count]; i++) { NSDictionary *freq1, *freq2; freq1 = [freqs objectAtIndex:i - 1]; freq2 = [freqs objectAtIndex:i]; NSInteger minLen = MIN([freq1 count], [freq2 count]); if (minLen != 0) { NSIndexSet *topIdxs1 = [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, MIN(3, [freq1 count]))]; NSIndexSet *topIdxs2 = [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, MIN(3, [freq2 count]))]; NSSet *freq1Top = [NSSet setWithArray:[[freq1 keysSortedByValueUsingComparator:cmpBlk] objectsAtIndexes:topIdxs1]]; NSSet *freq2Top = [NSSet setWithArray:[[freq2 keysSortedByValueUsingComparator:cmpBlk] objectsAtIndexes:topIdxs2]];

26

if ([freq1Top intersectsSet:freq2Top]) { NSLog(@Intersection between %@ and %@, freq1Top, freq2Top); [warnings addObject:[NSString stringWithFormat:fmtStr, i, i + 1]]; } } } if ([warnings count] == 0) { return @Looks good!; } return [warnings componentsJoinedByString:@\n]; }

- (NSDictionary *)analyze:(Workout *)workout { NSDictionary *dict = [NSDictionary dictionaryWithObject:[self analyzeForOveruse:workout] forKey:@Overuse]; return dict; }

e moveFrequencies code is implemented in FullTrack.m as follows:


- (NSDictionary *)moveFrequencies { NSArray *moveDescs = [self moveTypes]; NSMutableDictionary *freqs = [[NSMutableDictionary alloc] initWithCapacity:[moveDescs count]]; for (NSString *mv in moveDescs) { NSNumber *val = [freqs valueForKey:mv]; if (val == nil) { val = [NSNumber numberWithInt:1];

27

} else { val = [NSNumber numberWithInt:[val intValue] + 1]; } [freqs setValue:val forKey:mv]; } return freqs; }

Which in turn uses the moveTypes method,


- (NSArray *)moveTypes { NSMutableArray *moves = [[NSMutableArray alloc] init]; for (NSManagedObject *blk in [self blocks]) { for (NSManagedObject *seq in [blk valueForKey:@sequences]) { for (Move *move in [seq valueForKey:@moves]) { [moves addObject:[move moveDescriptionNub]]; } } } return moves; }

is method ultimately relies on the heuristics in Move.m, which uses regular expressions to try to determine the fundamental type of the move in question:
- (NSString *)moveDescriptionNub { NSError *err = nil; NSString *extracted = [self moveDescription]; NSArray *regexChain = [NSArray arrayWithObjects:

28

@^[A-Z][0-9]?:\\s*, @\\b(L|R|F|B|OTS)\\b.*$, @\\s+-\\s+.*$, @\\[(rotate|Repeat|eye) (icon|logo)\\]\\s*, @^\\d+x\\s*, nil]; NSRegularExpression *extractingRegex = nil; for (NSString *regex in regexChain) { extractingRegex = [NSRegularExpression regularExpressionWithPattern:regex options:0 error:&err]; if (err) { NSLog(@Error in regular expression: %@, err); return nil; } extracted = [extractingRegex stringByReplacingMatchesInString:extracted options:0 range:NSMakeRange(0, [extracted length]) withTemplate:@]; } return extracted; }

While very approximate, this chain of transformations has proven to be very eective on all test data, determining the basic type of move very successfully.

29

30

Appendix C Workout Model


As mentioned in chapter 3, various issues in CoreData preclude having Workout objects directly reference their associated Track objects, forcing us to use bare ids. However, we are able to use various language features to abstract over this, thereby hiding this implementation detail to the rest of the system, as shown below: In Workout.h:

@interface Workout : NSManagedObject

@property (nonatomic, retain) NSDate * createdAt; @property (nonatomic, retain) NSString * name; @property (nonatomic, strong) NSArray *tracks; - (void)removeTrackForSequence:(NSInteger)sequence; - (void)removeTracks:(NSArray *)tracks; - (void)addTrack:(FullTrack *)track; - (FullTrack *)trackForSequence:(NSInteger)sequence; - (NSArray *)fullTracks; @end

In Workout.m: 31

- (NSArray *)tracks { return [NSArray arrayWithObjects: [self track1Id], [self track2Id], [self track3Id], [self track4Id], [self track5Id], [self track6Id], [self track7Id], [self track8Id], [self track9Id], [self track10Id], [self track11Id], [self track12Id], nil]; }

- (void)setTracks:(NSArray *)tracks { for (FullTrack *track in tracks) { [self addTrack:track]; } }

- (void)addTrack:(FullTrack *)track { switch ([track.sequenceNumber intValue]) { case 1: [self setTrack1Id:track.trackId];

32

break; case 2: [self setTrack2Id:track.trackId]; break; case 3: [self setTrack3Id:track.trackId]; break; case 4: [self setTrack4Id:track.trackId]; break; case 5: [self setTrack5Id:track.trackId]; break; case 6: [self setTrack6Id:track.trackId]; break; case 7: [self setTrack7Id:track.trackId]; break; case 8: [self setTrack8Id:track.trackId]; break; case 9: [self setTrack9Id:track.trackId]; break; case 10: [self setTrack10Id:track.trackId]; break; case 11: [self setTrack11Id:track.trackId];

33

break; case 12: [self setTrack12Id:track.trackId]; break; default: NSLog(@Track with an unexpected sequence number: %@, track); break; } }

Essentially, using the

@property

feature of the Objective-C language, we are able to implement

methods that hide the nature of the representation of the tracks - to the consumer of this class, it appears that the workout object stores an array of tracks, rather than having multiple tracknId properties.

34

Das könnte Ihnen auch gefallen